GithubHelp home page GithubHelp logo

imageslr / taro-library Goto Github PK

View Code? Open in Web Editor NEW
348.0 6.0 70.0 1.45 MB

Taro + Redux + 本地 Mock Server 微信小程序示例项目

JavaScript 59.50% CSS 13.65% HTML 11.80% SCSS 14.27% Dockerfile 0.78%

taro-library's Introduction

项目简介

本项目是在线借书平台小程序使用 Taro 重构后的版本,仅包含三个示例页面,非常简单。面向人群主要是 Taro/React/Redux 的初学者,目的是提供一个简单的实践项目,帮助理解 Taro 与 Redux 的配合方式与 Taro 的基本使用。本项目还提供了一个快速搭建本地 mock 服务的解决方案。

因为我也是刚接触 Taro/React,所以只是分享一些开发经验,绕开一些小坑。如果觉得不错的话,请点右上角“⭐️Star”支持一下我,谢谢!如果有问题,欢迎提 issue;如果有任何改进,也欢迎 PR。

扫码体验:
code

技术栈

Taro + Taro UI + Redux + Webpack + ES6 + Mock

项目截图

UI

目录

运行项目

本项目在以下环境中编译通过:taro v1.2.20、nodejs v8.11.2、微信开发者工具最新版。

首先需要安装必要的环境:

# 安装 nvm,已经安装请忽略
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

# 安装 nodejs v8.11.2
nvm install v8.11.2

# 切换 node 版本
nvm use v8.11.2

# 检查 node 版本
node --version

## 安装 taro
npm install -g @tarojs/[email protected]

克隆项目:

git clone https://github.com/imageslr/taro-library.git
cd taro-library

启动小程序:

# 安装依赖
npm install // 或者 yarn

# 编译小程序
npm run dev:weapp

启动 mock 服务 (新建一个终端,在项目根目录下执行):

cd simplest-mock-server
npm install
gulp mock

之后在微信开发者工具中导入项目,即可预览。

开始学习

Taro 简介

Taro 是一个遵循 React 语法规范的多端开发解决方案。最近想学习 React,于是就想到使用 Taro 重构很早之前开发的在线借书平台小程序。虽然 Taro 上手有一定难度,但是其 React 框架比小程序原生更为灵活与规范,给我带来了非凡的开发体验。

在正式开始之前,您必须对 Taro 框架、 React 语法与小程序框架有一定的了解。此外,我建议您阅读以下文档,会更容易上手:

  • Taro 官方文档:必读,开发时也会随时查阅
  • Taro UI 官方文档:推荐,本项目使用 Taro UI 作为 UI 组件库
  • React 官方文档:必读,掌握 React 语法的必经之路,读完 MAIN CONCEPTS 部分就差不多了。对应的中文文档在这里,与英文版略有区别
  • Redux 文档:推荐,Redux 是最经常与 React 搭配使用的状态管理库。不过这个文档过于详实,读起来比较费劲,推荐你掌握 Redux 三大概念(Action、Reducer、Store)后直接在实践中体会 Redux 的原理与作用
  • React.js 小书:推荐,一步步从零构建 React 与 Redux,非常好的入门教程
  • Mock.js 文档:推荐,速查模拟数据占位符与模板

开发工具

开发工具:VS Code
代码规范:Prettier 插件 + ES Lint 插件

VS Code 对 JSX 与 TypeScript 有天然的支持,使用 VS Code 开发 Taro,不需要配置任何插件就能实现 Taro 组件的自动 import 与 props 提示,非常方便。

代码格式化插件我选择 Prettier,它屏蔽了很多配置项,强制遵循约定的规范。与之类似的格式化插件还有 Beautify,不过我更喜欢 Prettier 对 JSX 属性强制自动换行的风格。

ES Lint 是 JavaScript 与 JSX 的静态检测工具,安装 ES Lint 插件后在代码编写阶段就可以检测到不易发现的错误(如为常量赋值、变量未使用、变量未定义等等)。Taro 已经定义了一套 ES Lint 规则集,使用 taro-cli 生成的 Taro 项目基本不需要再作额外配置。

样式规范

CSS 预处理器

Taro UI 定义了很多变量可复用的 mixins。为了与 Taro UI 样式风格保持一致,本项目采用 Taro UI 所使用的 Sass 作为 CSS 预处理器。

布局

优先使用 Flex 布局。学习 Flex 布局可以参考这两篇文章:

Taro UI 封装了一些常用的 Flex 样式类,包括:

  • 1~12 的栅格化长度类at-col-1at-col-2
  • 栅格化偏移类at-col__offset-1
  • flex属性:超出换行at-row--wrap,宽度根据内容撑开at-col--auto
  • 对齐方式、排列方式

不过 Taro UI 并没有为flex: none;提供样式类。

BEM 命名规范

关于 BEM,网上有很多的教程,就不再细说了。Block__Element--Modifier的命名方式在 Sass 中很容易描述:

.block {
  //...
  &__element {
    //...
    &--modifier {
      //...
    }
  }
}

组件样式

对于/components目录下的可复用组件,使用my作为命名空间,避免被全局样式污染,比如my-panelmy-search-bar等。

组件可以使用externalClasses定义若干个外部样式类,或者开启options.addGlobalClass以使用全局样式。见Taro 文档 - 组件的外部样式和全局样式

如果希望能够在组件的props中直接传递className或者style,比如这样:

// index.jsx
<MyComponent className='custom-class' style={/* ... */}>

Taro 默认并不支持这一写法。我们可以将classNamecustomStyle作为组件的props,然后在render()中手动将这两个props添加到根元素上:

// my-component.jsx
export default MyComponent extends Component {
  static options = {
    addGlobalClass: true
  }

  static defaultProps = {
    className: '',
    customStyle: {}
  }

  render () {
    const { className, customStyle } = this.props
    return <View
      className={'my-class ' + className}
      style={customStyle}
    >
      组件内容
    </View>
  }
}

尺寸单位

Taro 文档 - 设计稿及尺寸单位

Taro 的尺寸单位是px,默认的尺寸稿是 iPhone 6 750px。Taro 会 1:1 地将px转为小程序的rpx。而在小程序中,pxrpx是 1:2 的关系。如果希望字体采用浏览器的默认大小14px,那么应该这么写:

  • Taro:28px
  • Taro:14PX
  • Taro JSX 行内样式:Taro.pxTransform(14)
  • 小程序原生:28rpx

Taro 会将有大写字母的PxPX忽略,但是 VS Code 在使用 Prettier 插件时会自动将PxPX转为px。对于这个问题,有两种解决方案:

  • 换用 Beautify 插件
  • 在包含大写字母的属性的前一行添加/* prettier-ignore */
    /* prettier-ignore */
    $input-padding: 25PX;

项目初始化

$ taro init taro-library
> ...
> ? 请输入项目介绍! Taro图书小程序
> ? 是否需要使用 TypeScript ? No
> ? 请选择 CSS 预处理器(Sass/Less/Stylus) Sass
> ? 请选择模板 Redux 模板
>
> ✔ 创建项目: taro-library

安装项目依赖:

$ npm install taro-ui --save

引入 Redux

Redux 文件设置

在初始化的时候,我们选择了 Redux 模板。打开文件夹,可以看到 Taro 创建了一个示例页面,redux 相关的文件夹为:

├── actions
│   └── counter.js
├── constants
│   └── counter.js
├── reducers
│   ├── counter.js
│   └── index.js
└── store
    └── index.js

这种方式是按照 Redux 的组成部分来划分的,/constantsaction-type字符串的声明文件,不同文件夹中的同名文件对应同一份数据。

另一种划分方式是将同一份数据的所有文件组合在同一个文件夹里:

└── store
    ├── counter
    │   ├── action-type.js // 对应/constants/counter.js
    │   ├── action.js // 对应/actions/counter.js
    │   └── reducer.js // 对应/reducers/counter.js
    ├── home
    │   ├── action-type.js
    │   ├── action.js
    │   └── reducer.js
    ├── index.js // 对应/store/index.js
    └── rootReducer.js // 对应/reducer/index.js

本项目采用第二种方式管理 Redux 数据。Taro 生成的 Redux 模板中已经添加了redux-logger中间件实现日志打印功能。

代码见 dev-redux-init 分支

connect 方法

推荐先阅读 Redux 文档

使用 Redux 之后,我们可以将数据存储在store中,通过action操作数据。那么怎么在组件中访问与操作数据呢?react-redux提供了connect方法,允许我们将store中的数据与action作为props绑定到组件上。

从原理上来讲,connect方法返回的是一个高阶组件。这个高阶组件会对原组件进行包装,然后返回新的组件。不过我们这里不讲connect的细节,只讲它的使用方法。有关connect方法与 Redux 的原理,推荐阅读 React.js 小书

参数

connect接收四个参数,分别是mapStateToPropsmapDispatchToPropsmergePropsoptions。本项目只用到了前两个参数。

mapStateToProps

mapStateToProps是一个函数,它将store中的数据映射到组件的props上。mapStateToProps接收两个参数:stateownProps。第一个参数就是 Redux 的store,第二个数据是组件自己的props

举个例子:

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

这段代码的功能是将store中的count属性的值,映射到组件的 this.props.count 上。当我们访问this.props.count时,输出的就是store.count的值。当store.count值变化时,组件也会同步更新。

我们还可以使用 ES6 的对象解构赋值、属性简写和箭头函数等语法,进一步简化上面的代码:

const mapStateToProps = ({ count }) => ({
  count
});

有时候我们需要根据组件自身的props作一些条件判断,这时候就需要用到第二个参数。

mapDispatchToProps

mapDispatchToProps也是一个函数,它接收两个参数:dispatchownProps。第一个参数就是 Redux 的dispatch方法,第二个数据是组件自己的props。它的功能是将action作为props绑定到组件上。

举个例子:

import { add, minus, asyncAdd } from "@store/counter/action";

const mapDispatchToProps = (dispatch) => {
  return {
    add() {
      dispatch(add());
    },
    dec() {
      dispatch(minus());
    },
    asyncAdd() {
      dispatch(asyncAdd());
    }
  }
}

当我们调用this.props.add时,实际上是在调用dispatch(add())

使用 connect 方法

使用connect方法将组件与 Redux 结合:

import { add, minus, asyncAdd } from "@store/counter/action";

// 首先定义组件
class MyComponent extends Component {
  render() {
    return;
    <View>
      <Button onClick={this.props.add}>点击 + 1</Button>
      <View>计数:{this.props.count}</View>
    </View>;
  }
}

// 定义 mapStateToProps
const mapStateToProps = ({ count }) => ({
  count
});

// 定义 mapDispatchToProps
const mapDispatchToProps = dispatch => {
  return {
    add() {
      dispatch(add());
    }
  };
};

// 使用 connect 方法,export 包装后的新组件
export connect(mapStateToProps, mapDispatchToProps)(MyComponent);

这种分散的写法不利于我们查看组件从 Redux 中引入了多少props。我们可以使用 ES6 的装饰器语法进一步改造它:

import { add, minus, asyncAdd } from "@store/counter/action";

@connect(
  ({ counter }) => ({
    counter
  }),
  dispatch => ({
    add() {
      dispatch(add());
    }
  })
)
class MyComponent extends Component {
  render() {
    return;
    <View>
      <Button onClick={this.props.add}>点击 + 1</Button>
      <View>计数:{this.props.count}</View>
    </View>;
  }
}

export default MyComponent;

我们甚至可以使用对象形式来传递mapDispatchToProps,获得更简化的写法:

@connect(
  ({ counter }) => ({
    counter
  }),
  {
    // 调用 this.props.dispatchAdd() 相当于
    // 调用 dispatch(add())
    dispatchAdd: add,
    dispatchMinus: minus,
    // ...
  }
)

这就是 Taro 组件与 Redux 结合的最终形式。

异步 Action

异步 Action 返回的是一个参数为dispatch的函数,这个函数本身也可以被dispatch。我们只需要在 Redux 中引入redux-thunk中间件,就可以使用异步 Action。关于异步 Action 的原理,可以查看Redux 官方文档

Taro Redux 模板提供了一个异步 Action 的简单示例:

/* /store/counter/action.js */
export function asyncAdd() {
  return dispatch => {
    setTimeout(() => {
      dispatch(add());
    }, 2000);
  };
}

// 组件中
@connect(
  ({ counter }) => ({
    counter
  }),
  dispatch => ({
    asyncAdd() {
      dispatch(asyncAdd());
    }
  })
)
class MyComponent extends Component {
  render () {
    return <Button onClick={this.props.asyncAdd}>点击 + 1</Button>
  }
}

可以看到,异步 Action 和常规 Action 在使用上并没有任何区别。

API 封装

Taro 已经封装了网络请求,支持 Promise 化使用。本项目对Taro.request()进一步封装,以便统一管理接口、根据不同环境选择不同域名、设置请求拦截器、响应拦截器等。完整代码见 /src/service 文件夹。

域名切换

生产环境使用线上接口,开发环境使用本地接口。新建/service/config.js文件:

export default BASE_URL =
  process.env.NODE_ENV === "development"
    ? "http://localhost:3000" // 开发环境,需要开启mock server(执行:gulp mock)
    : "TODO"; // 生产环境,线上服务器

封装请求

代码见 /src/service/api.js,代码非常简单。访问后台所需要的认证信息(token)可以添加在option.header中。

添加拦截器

Taro 支持添加拦截器,可以使用拦截器在请求发出前后做一些额外操作。

为什么要用拦截器呢?设想一下网络请求的场景。我们的目的是发出一个网络请求并接收响应,但是在发出请求之前,我们可能需要检查数据、添加用户的权限信息;如果项目大一些,我们可能还需要在发出请求之前先上报统计数据。这一系列流程之后才能真正执行我们的目标操作:网络请求。而获取到服务器响应后,我们还需要根据状态码执行不同的操作:401/403 跳转到登录页面,404 跳转到空白页面,500 展示错误信息...

可以看到,如果将这些流程的代码都写到一起,那么代码将又长又乱,十分复杂。

我们可以使用拦截器来解决这个问题。拦截器就是中间件,可以帮助我们优雅地分离业务逻辑。我们将每一个业务逻辑写成一个拦截器,在每个拦截器中,只需要关注当前阶段的代码实现。

中间件的处理流程又称为洋葱模型,其执行过程是:先从最外层中间件从外到内依次执行到核心程序,再从核心程序从内到外依次执行到最外层中间件,每一个中间件的执行参数均是前一个中间件的返回值。如下图所示:

下面是一个简单的中间件/拦截器示例代码:

/**
 * @param {object} req request对象
 * @param {function} next 调用下一个中间件的函数
 */
function interceptor(req, next) {
  // 在下一个中间件执行之前做一些操作...
  // 比如添加一个参数
  req.token = 'token'

  // 执行下一个中间件...
  // 保存其返回值
  var res = next(req)

  // 在下一个中间件返回结果之后做一些操作...
  // 比如判断服务器返回的状态码
  if(res.status == 401){
    // ...
  }
  return res
}

Taro.request的拦截器函数与上例略有不同,将拦截器的调用方法改为了异步的形式:

/**
 * @param {object} chain.requestParmas request对象
 * @param {function} chain.proceed  调用下一个中间件的函数
 */
function interceptor(chain) {
  // 在下一个中间件执行之前做一些操作...
  // 比如添加一个参数
  var requestParmas = chain.requestParmas;
  requestParmas.token = "token";

  // 执行下一个中间件...
  return chain.proceed(requestParmas).then(res => {
    // 在下一层行动返回结果之后做一些操作...
    // 比如判断服务器返回的状态码
    if (res.status == 401) {
      // ...
    }
    return res;
  });
}

采用拦截器有利于代码解耦,符合高内聚低耦合的原则。本项目将拦截器定义在一个单独的文件中,以数组形式统一导出。使用 Taro 内置拦截器Taro.interceptors.logInterceptor打印请求的相关信息。代码见 /src/service/interceptors.js

async 和 await

最后,当我们发起网络请求时,可以使用 ES6 的async/await语法代替 Promise 对象,能大大提高代码的可读性。关于 async 和 await 的原理,可以查看理解 JavaScript 的 async/await

一个简单示例:

// API.get() 返回一个 Promise 对象
// Promise 方法调用
function getBook(id) {
  API.get(`/books/${id}`).then(res => {
    this.setState({book: res});
  }).catch(e => {
    console.error(e);
  })
}

// async/await 语法调用
async function getBook(id) {
  try {
    const book = API.get(`/books/${id}`);
    this.setState({book: res});
  } catch(e) {
    console.error(e)
  }
}

搭建本地 mock 服务

常见的 mock 平台有 EasyMock、rap2 等,不过这些网站有时候响应较慢,调试起来也不太方便,因此在本地搭建一个 mock 服务器是更好的选择。

搭建本地 mock 服务器有几种思路,如本地安装 EasyMock,或者 php 简单写几行返回数据的代码,但是这些都需要安装额外的运行环境,工作量较大。这里我选择了 simplest-mock-server,一个开箱即用的 RESTful API mock 服务,详细使用教程见文档

启动 mock 服务的方法如下:

# 在本项目根目录下执行
cd simplest-mock-server

# 安装依赖
npm install

# 启动 mock 服务
gulp mock

关闭gulp mock终端进程,模拟网络中断场景;修改 /simplest-mock-server/server.js 中的延迟时长,模拟 timeout 场景。

接口配置位于 /simplest-mock-server/example,改动此文件夹下的任何内容,均会实时刷新 mock 服务器。

之后就享受愉快的开发过程吧!

搭建云托管 mock 服务

本项目的线上版本使用微信云托管提供 mock 接口。需要升级 taro 到 v3.0,代码改动较大,因此单独放在 cloud 分支。微信云托管的搭建教程请查看 weapp-library

其他补充

Taro JSX

不能在render()以外的函数中返回 JSX,也就是说下面这种写法是不允许的:

renderA() {
  return <View>A</View>
}

renderB() {
  return <View>B</View>
}

render () {
  return (
    <View>
      {someCondition1 && this.renderA()}
      {someCondition2 && this.renderB()}
    </View>
  )
}

Taro 生命周期

Taro 编译到小程序端后,每个组件的constructor首先会被调用一次(即使没有实例化),见Taro 文档

constructor中初始化state,在componentDidMount中发起网络请求,componentWillMount不知道有什么用。更多有关生命周期的知识,请查看 Taro 文档 React 组件生命周期

运行配置相关

允许在 sass 中通过别名引入其他 sass 文件

在 sass 中通过别名(@ 或 ~)引用其他 sass 文件,有两个解决方法

  1. 在 js 中用import '~taro-ui/dist/style/index.scss'引入
  2. 增加 sass 的 importer 配置,可参考 https://github.com/js-newbee/taro-yanxuan/blob/master/config/index.js

本项目采用的是第二种方法。

引入 iconfont 图标

参考 Taro UI 文档

taro-library's People

Contributors

elonzzz avatar imageslr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

taro-library's Issues

求助:运行不起来啊,跟着这个guide

跟着这个文档 运行不起来啊
app.js:39 Uncaught TypeError: Cannot read property 'Provider' of undefined
at app.js:39
at require (WAService.js:1)
at :1:1
at HTMLScriptElement.scriptLoaded (appservice?t=1567478069631:2041)
at HTMLScriptElement.script.onload (appservice?t=156747806963

按你写出现了这个问题

这怎么发图片好,在pages/home/index
点击新书速递时他跳转页面是pages/home/pages/book-list/index?type=new
(VM17535:1 navigateTo:fail page "pages/home/pages/book-list/index?type=new" is not found)
1111

h5下报错

book-list 的 constructor里的这些改到 componentWillMount里咯,要不会报错

componentWillMount(){
    const { type } = this.$router.params;
    switch (type) {
      case "new":
        return Taro.setNavigationBarTitle({ title: "新书速递" });
      case "hot":
        return Taro.setNavigationBarTitle({ title: "近期热门" });
      case "recommend":
        return Taro.setNavigationBarTitle({ title: "为你推荐" });
    }
  }

另外试了一下,支付宝小程序可以跑起来

您好,小程序运行后,报这个错误,请指导一下?

index.js:85 Uncaught ReferenceError: regeneratorRuntime is not defined
at index.js:85
at index.js:84
at index.js:31
at require (WAService.js:1)
at :137:7
at HTMLScriptElement.scriptLoaded (appservice?t=1570516467306:2041)
at HTMLScriptElement.script.onload (appservice?t=1570516467306:2084)

cloud分支: mock-server启动报错TypeError: router[method] is not a function

simplest-mock-server> gulp mock
[23:53:07] Using gulpfile \taro-library-cloud\simplest-mock-server\gulpfile.js
[23:53:07] Starting 'mock'...
[23:53:07] Finished 'mock' after 51 ms
[23:53:07] [nodemon] 2.0.7
[23:53:07] [nodemon] to restart at any time, enter `rs`
[23:53:07] [nodemon] watching path(s): example\**\* server.js router.js
[23:53:07] [nodemon] watching extensions: js,json
[23:53:07] [nodemon] starting `node ./server.js --dir example --port 3000`
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books\hot.json
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books\isbn\{isbn}.json
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books\new.json
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books\recommend.json
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books\{id}.json
[INFO] Reading mock template file: \taro-library-cloud\simplest-mock-server\example\GET\books.json
\taro-library-cloud\simplest-mock-server\router.js:69
  router[method](pathname, handler);
                ^

TypeError: router[method] is not a function
    at routes.forEach (D:\taro-library-cloud\simplest-mock-server\router.js:69:17)
    at Array.forEach (<anonymous>)
    at Object.<anonymous> (D:\programs\demo\taro-library-cloud\simplest-mock-server\router.js:50:8)
    at Module._compile (internal/modules/cjs/loader.js:736:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:747:10)
    at Module.load (internal/modules/cjs/loader.js:628:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:568:12)
    at Function.Module._load (internal/modules/cjs/loader.js:560:3)
    at Module.require (internal/modules/cjs/loader.js:665:17)
    at require (internal/modules/cjs/helpers.js:20:18)
application has crashed!

请问下这个是缺少了什么依赖么?node: v11.8.0,已执行npm install
(PS:按12版本去安装,也报错:fs.js:45 } = primordials; ^ ReferenceError: primordials is not defined ,网上给出的答案是node12版本不支持gulp4以下版本)
QAQ,求解惑

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.