{"id":13522578,"url":"https://github.com/imageslr/taro-library","last_synced_at":"2025-04-06T11:10:31.936Z","repository":{"id":49852187,"uuid":"177940766","full_name":"imageslr/taro-library","owner":"imageslr","description":" Taro + Redux + 本地 Mock Server 微信小程序示例项目","archived":false,"fork":false,"pushed_at":"2022-02-13T14:33:28.000Z","size":1482,"stargazers_count":365,"open_issues_count":1,"forks_count":69,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-30T10:07:24.811Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imageslr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-27T07:28:31.000Z","updated_at":"2025-03-24T12:11:27.000Z","dependencies_parsed_at":"2022-08-12T20:50:29.396Z","dependency_job_id":null,"html_url":"https://github.com/imageslr/taro-library","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imageslr%2Ftaro-library","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imageslr%2Ftaro-library/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imageslr%2Ftaro-library/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imageslr%2Ftaro-library/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imageslr","download_url":"https://codeload.github.com/imageslr/taro-library/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247471521,"owners_count":20944158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T06:00:49.315Z","updated_at":"2025-04-06T11:10:31.913Z","avatar_url":"https://github.com/imageslr.png","language":"JavaScript","funding_links":[],"categories":["实战项目","示例项目","Demo"],"sub_categories":["小程序实战项目","Taro 1","可以直接运行成功"],"readme":"## 项目简介\n\n本项目是[在线借书平台](https://github.com/imageslr/weapp-library)小程序使用 [Taro](https://nervjs.github.io/taro/docs/GETTING-STARTED.html) 重构后的版本，仅包含三个示例页面，非常简单。面向人群主要是 Taro/React/Redux 的初学者，目的是提供一个简单的实践项目，帮助理解 Taro 与 Redux 的配合方式与 Taro 的基本使用。本项目还提供了一个快速搭建本地 mock 服务的解决方案。\n\n因为我也是刚接触 Taro/React，所以只是分享一些开发经验，绕开一些小坑。如果觉得不错的话，请点右上角“⭐️Star”支持一下我，谢谢！如果有问题，欢迎提 issue；如果有任何改进，也欢迎 PR。\n\n扫码体验：  \n![code](./assets/qrcode.jpg)\n\n## 技术栈\n\nTaro + Taro UI + Redux + Webpack + ES6 + Mock\n\n## 项目截图\n\n![UI](./assets/ui.png)\n\n## 目录\n\n- [项目简介](#项目简介)\n- [技术栈](#技术栈)\n- [项目截图](#项目截图)\n- [目录](#目录)\n- [运行项目](#运行项目)\n- [开始学习](#开始学习)\n  - [Taro 简介](#taro-简介)\n  - [开发工具](#开发工具)\n  - [样式规范](#样式规范)\n    - [CSS 预处理器](#css-预处理器)\n    - [布局](#布局)\n    - [BEM 命名规范](#bem-命名规范)\n    - [组件样式](#组件样式)\n    - [尺寸单位](#尺寸单位)\n- [项目初始化](#项目初始化)\n- [引入 Redux](#引入-redux)\n  - [Redux 文件设置](#redux-文件设置)\n  - [connect 方法](#connect-方法)\n    - [参数](#参数)\n    - [mapStateToProps](#mapstatetoprops)\n    - [mapDispatchToProps](#mapdispatchtoprops)\n  - [使用 connect 方法](#使用-connect-方法)\n  - [异步 Action](#异步-action)\n- [API 封装](#api-封装)\n  - [域名切换](#域名切换)\n  - [封装请求](#封装请求)\n  - [添加拦截器](#添加拦截器)\n  - [async 和 await](#async-和-await)\n- [搭建本地 mock 服务](#搭建本地-mock-服务)\n- [搭建云托管 mock 服务](#搭建云托管-mock-服务)\n- [其他补充](#其他补充)\n  - [Taro JSX](#taro-jsx)\n  - [Taro 生命周期](#taro-生命周期)\n  - [运行配置相关](#运行配置相关)\n    - [允许在 sass 中通过别名引入其他 sass 文件](#允许在-sass-中通过别名引入其他-sass-文件)\n    - [引入 iconfont 图标](#引入-iconfont-图标)\n\n## 运行项目\n\n本项目在以下环境中编译通过：taro v1.2.20、nodejs v8.11.2、微信开发者工具最新版。\n\n首先需要安装必要的环境：\n```\n# 安装 nvm，已经安装请忽略\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash\n\n# 安装 nodejs v8.11.2\nnvm install v8.11.2\n\n# 切换 node 版本\nnvm use v8.11.2\n\n# 检查 node 版本\nnode --version\n\n## 安装 taro\nnpm install -g @tarojs/cli@1.2.20\n```\n\n克隆项目：\n```\ngit clone https://github.com/imageslr/taro-library.git\ncd taro-library\n```\n\n启动小程序：\n```\n# 安装依赖\nnpm install // 或者 yarn\n\n# 编译小程序\nnpm run dev:weapp\n```\n\n启动 mock 服务 (新建一个终端，在项目根目录下执行)：\n```\ncd simplest-mock-server\nnpm install\ngulp mock\n```\n\n之后在微信开发者工具中导入项目，即可预览。\n\n## 开始学习\n\n### Taro 简介\n\n[Taro](https://nervjs.github.io/taro/docs/GETTING-STARTED.html) 是一个遵循 [React](https://reactjs.org/) 语法规范的多端开发解决方案。最近想学习 React，于是就想到使用 Taro 重构很早之前开发的[在线借书平台](https://github.com/imageslr/weapp-library)小程序。虽然 Taro 上手有一定难度，但是其 React 框架比小程序原生更为灵活与规范，给我带来了非凡的开发体验。\n\n在正式开始之前，您必须对 Taro 框架、 React 语法与小程序框架有一定的了解。此外，我建议您阅读以下文档，会更容易上手：\n\n- [Taro 官方文档](https://nervjs.github.io/taro/docs/README.html)：必读，开发时也会随时查阅\n- [Taro UI 官方文档](https://taro-ui.aotu.io/#/docs/introduction)：推荐，本项目使用 Taro UI 作为 UI 组件库\n- [React 官方文档](https://reactjs.org/docs/hello-world.html)：必读，掌握 React 语法的必经之路，读完 MAIN CONCEPTS 部分就差不多了。对应的中文文档[在这里](https://react.docschina.org/docs/hello-world.html)，与英文版略有区别\n- [Redux 文档](https://www.redux.org.cn/)：推荐，Redux 是最经常与 React 搭配使用的状态管理库。不过这个文档过于详实，读起来比较费劲，推荐你掌握 Redux 三大概念（Action、Reducer、Store）后直接在实践中体会 Redux 的原理与作用\n- [React.js 小书](http://huziketang.mangojuice.top/books/react/)：推荐，一步步从零构建 React 与 Redux，非常好的入门教程\n- [Mock.js 文档](http://mockjs.com/examples.html)：推荐，速查模拟数据占位符与模板\n\n### 开发工具\n\n开发工具：VS Code  \n代码规范：[Prettier](https://prettier.io/) 插件 + ES Lint 插件\n\nVS Code 对 JSX 与 TypeScript 有天然的支持，使用 VS Code 开发 Taro，不需要配置任何插件就能实现 Taro 组件的自动 import 与 props 提示，非常方便。\n\n代码格式化插件我选择 [Prettier](https://prettier.io/)，它屏蔽了很多配置项，强制遵循约定的规范。与之类似的格式化插件还有 Beautify，不过我更喜欢 Prettier 对 JSX 属性强制自动换行的风格。\n\nES Lint 是 JavaScript 与 JSX 的静态检测工具，安装 ES Lint 插件后在代码编写阶段就可以检测到不易发现的错误（如为常量赋值、变量未使用、变量未定义等等）。Taro 已经定义了一套 ES Lint 规则集，使用 taro-cli 生成的 Taro 项目基本不需要再作额外配置。\n\n### 样式规范\n\n#### CSS 预处理器\n\n[Taro UI](https://taro-ui.aotu.io/#/docs/introduction) 定义了很多[变量](https://github.com/NervJS/taro-ui/blob/dev/src/style/variables/default.scss)与[可复用的 mixins](https://github.com/NervJS/taro-ui/tree/dev/src/style/mixins/libs)。为了与 Taro UI 样式风格保持一致，本项目采用 Taro UI 所使用的 Sass 作为 CSS 预处理器。\n\n#### 布局\n\n优先使用 Flex 布局。学习 Flex 布局可以参考这两篇文章：\n\n- [阮一峰 - Flex 布局教程：语法篇](http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html)\n- [阮一峰 - Flex 布局教程：实例篇](http://www.ruanyifeng.com/blog/2015/07/flex-examples.html)\n\nTaro UI 封装了一些常用的 [Flex 样式类](https://taro-ui.aotu.io/#/docs/flex)，包括：\n\n- 1~12 的栅格化长度类`at-col-1`、`at-col-2`等\n- 栅格化偏移类`at-col__offset-1`等\n- `flex`属性：超出换行`at-row--wrap`，宽度根据内容撑开`at-col--auto`\n- 对齐方式、排列方式\n\n不过 Taro UI 并没有为`flex: none;`提供样式类。\n\n#### BEM 命名规范\n\n关于 BEM，网上有很多的教程，就不再细说了。`Block__Element--Modifier`的命名方式在 Sass 中很容易描述：\n\n```less\n.block {\n  //...\n  \u0026__element {\n    //...\n    \u0026--modifier {\n      //...\n    }\n  }\n}\n```\n\n#### 组件样式\n\n对于`/components`目录下的可复用组件，使用`my`作为命名空间，避免被全局样式污染，比如`my-panel`、`my-search-bar`等。\n\n组件可以使用`externalClasses`定义若干个外部样式类，或者开启`options.addGlobalClass`以使用全局样式。见[Taro 文档 - 组件的外部样式和全局样式](https://nervjs.github.io/taro/docs/component-style.html)。\n\n如果希望能够在组件的`props`中直接传递`className`或者`style`，比如这样：\n\n```JSX\n// index.jsx\n\u003cMyComponent className='custom-class' style={/* ... */}\u003e\n```\n\nTaro 默认并不支持这一写法。我们可以将`className`和`customStyle`作为组件的`props`，然后在`render()`中手动将这两个`props`添加到根元素上：\n\n```JSX\n// my-component.jsx\nexport default MyComponent extends Component {\n  static options = {\n    addGlobalClass: true\n  }\n\n  static defaultProps = {\n    className: '',\n    customStyle: {}\n  }\n\n  render () {\n    const { className, customStyle } = this.props\n    return \u003cView\n      className={'my-class ' + className}\n      style={customStyle}\n    \u003e\n      组件内容\n    \u003c/View\u003e\n  }\n}\n```\n\n#### 尺寸单位\n\n[Taro 文档 - 设计稿及尺寸单位](https://nervjs.github.io/taro/docs/size.html)\n\nTaro 的尺寸单位是`px`，默认的尺寸稿是 iPhone 6 750px。Taro 会 1:1 地将`px`转为小程序的`rpx`。而在小程序中，`px`与`rpx`是 1:2 的关系。如果希望字体采用浏览器的默认大小`14px`，那么应该这么写：\n\n- Taro：`28px`\n- Taro：`14PX`\n- Taro JSX 行内样式：`Taro.pxTransform(14)`\n- 小程序原生：`28rpx`\n\nTaro 会将有大写字母的`Px`或`PX`忽略，但是 VS Code 在使用 Prettier 插件时会自动将`Px`或`PX`转为`px`。对于这个问题，有两种解决方案：\n\n- 换用 Beautify 插件\n- 在包含大写字母的属性的前一行添加`/* prettier-ignore */`\n  ```scss\n  /* prettier-ignore */\n  $input-padding: 25PX;\n  ```\n\n## 项目初始化\n\n```\n$ taro init taro-library\n\u003e ...\n\u003e ? 请输入项目介绍！ Taro图书小程序\n\u003e ? 是否需要使用 TypeScript ？ No\n\u003e ? 请选择 CSS 预处理器（Sass/Less/Stylus） Sass\n\u003e ? 请选择模板 Redux 模板\n\u003e\n\u003e ✔ 创建项目: taro-library\n```\n\n安装项目依赖：\n\n```\n$ npm install taro-ui --save\n```\n\n## 引入 Redux\n\n### Redux 文件设置\n\n在初始化的时候，我们选择了 Redux 模板。打开文件夹，可以看到 Taro 创建了一个示例页面，redux 相关的文件夹为：\n\n```\n├── actions\n│   └── counter.js\n├── constants\n│   └── counter.js\n├── reducers\n│   ├── counter.js\n│   └── index.js\n└── store\n    └── index.js\n```\n\n这种方式是按照 Redux 的组成部分来划分的，`/constants`是`action-type`字符串的声明文件，不同文件夹中的同名文件对应同一份数据。\n\n另一种划分方式是将同一份数据的所有文件组合在同一个文件夹里：\n\n```\n└── store\n    ├── counter\n    │   ├── action-type.js // 对应/constants/counter.js\n    │   ├── action.js // 对应/actions/counter.js\n    │   └── reducer.js // 对应/reducers/counter.js\n    ├── home\n    │   ├── action-type.js\n    │   ├── action.js\n    │   └── reducer.js\n    ├── index.js // 对应/store/index.js\n    └── rootReducer.js // 对应/reducer/index.js\n```\n\n本项目采用第二种方式管理 Redux 数据。Taro 生成的 Redux 模板中已经添加了`redux-logger`中间件实现日志打印功能。\n\n代码见 [dev-redux-init 分支](https://github.com/imageslr/taro-library/tree/dev-redux-init)。\n\n### connect 方法\n\n推荐先阅读 [Redux 文档](https://www.redux.org.cn/)。\n\n使用 Redux 之后，我们可以将数据存储在`store`中，通过`action`操作数据。那么怎么在组件中访问与操作数据呢？`react-redux`提供了`connect`方法，允许我们将`store`中的数据与`action`作为`props`绑定到组件上。\n\n从原理上来讲，`connect`方法返回的是一个高阶组件。这个高阶组件会对原组件进行包装，然后返回新的组件。不过我们这里不讲`connect`的细节，只讲它的使用方法。有关`connect`方法与 Redux 的原理，推荐阅读 [React.js 小书](http://huziketang.mangojuice.top/books/react/)。\n\n#### 参数\n\n`connect`接收四个参数，分别是`mapStateToProps`、`mapDispatchToProps`、`mergeProps`和`options`。本项目只用到了前两个参数。\n\n#### mapStateToProps\n\n`mapStateToProps`是一个函数，它将`store`中的数据映射到组件的`props`上。`mapStateToProps`接收两个参数：`state`、`ownProps`。第一个参数就是 Redux 的`store`，第二个数据是组件自己的`props`。\n\n举个例子：\n\n```JS\nconst mapStateToProps = (state) =\u003e {\n  return {\n    count: state.count\n  }\n}\n```\n\n这段代码的功能是将`store`中的`count`属性的值，映射到组件的 `this.props.count` 上。当我们访问`this.props.count`时，输出的就是`store.count`的值。当`store.count`值变化时，组件也会同步更新。\n\n我们还可以使用 ES6 的对象[解构赋值](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)、属性简写和箭头函数等语法，进一步简化上面的代码：\n\n```js\nconst mapStateToProps = ({ count }) =\u003e ({\n  count\n});\n```\n\n有时候我们需要根据组件自身的`props`作一些条件判断，这时候就需要用到第二个参数。\n\n#### mapDispatchToProps\n\n`mapDispatchToProps`也是一个函数，它接收两个参数：`dispatch`、`ownProps`。第一个参数就是 Redux 的`dispatch`方法，第二个数据是组件自己的`props`。它的功能是将`action`作为`props`绑定到组件上。\n\n举个例子：\n\n```JS\nimport { add, minus, asyncAdd } from \"@store/counter/action\";\n\nconst mapDispatchToProps = (dispatch) =\u003e {\n  return {\n    add() {\n      dispatch(add());\n    },\n    dec() {\n      dispatch(minus());\n    },\n    asyncAdd() {\n      dispatch(asyncAdd());\n    }\n  }\n}\n```\n\n当我们**调用**`this.props.add`时，实际上是在调用`dispatch(add())`。\n\n### 使用 connect 方法\n\n使用`connect`方法将组件与 Redux 结合：\n\n```jsx\nimport { add, minus, asyncAdd } from \"@store/counter/action\";\n\n// 首先定义组件\nclass MyComponent extends Component {\n  render() {\n    return;\n    \u003cView\u003e\n      \u003cButton onClick={this.props.add}\u003e点击 + 1\u003c/Button\u003e\n      \u003cView\u003e计数：{this.props.count}次\u003c/View\u003e\n    \u003c/View\u003e;\n  }\n}\n\n// 定义 mapStateToProps\nconst mapStateToProps = ({ count }) =\u003e ({\n  count\n});\n\n// 定义 mapDispatchToProps\nconst mapDispatchToProps = dispatch =\u003e {\n  return {\n    add() {\n      dispatch(add());\n    }\n  };\n};\n\n// 使用 connect 方法，export 包装后的新组件\nexport connect(mapStateToProps， mapDispatchToProps)(MyComponent);\n```\n\n这种分散的写法不利于我们查看组件从 Redux 中引入了多少`props`。我们可以使用 ES6 的[装饰器语法](https://aotu.io/notes/2016/10/24/decorator/index.html)进一步改造它：\n\n```JSX\nimport { add, minus, asyncAdd } from \"@store/counter/action\";\n\n@connect(\n  ({ counter }) =\u003e ({\n    counter\n  }),\n  dispatch =\u003e ({\n    add() {\n      dispatch(add());\n    }\n  })\n)\nclass MyComponent extends Component {\n  render() {\n    return;\n    \u003cView\u003e\n      \u003cButton onClick={this.props.add}\u003e点击 + 1\u003c/Button\u003e\n      \u003cView\u003e计数：{this.props.count}次\u003c/View\u003e\n    \u003c/View\u003e;\n  }\n}\n\nexport default MyComponent;\n```\n\n我们甚至可以使用对象形式来传递`mapDispatchToProps`，获得更简化的写法：\n\n```js\n@connect(\n  ({ counter }) =\u003e ({\n    counter\n  }),\n  {\n    // 调用 this.props.dispatchAdd() 相当于\n    // 调用 dispatch(add())\n    dispatchAdd: add,\n    dispatchMinus: minus,\n    // ...\n  }\n)\n```\n\n这就是 Taro 组件与 Redux 结合的最终形式。\n\n### 异步 Action\n\n异步 Action 返回的是一个参数为`dispatch`的函数，这个函数本身也可以被`dispatch`。我们只需要在 Redux 中引入`redux-thunk`中间件，就可以使用异步 Action。关于异步 Action 的原理，可以查看[Redux 官方文档](https://www.redux.org.cn/docs/advanced/AsyncActions.html)。\n\nTaro Redux 模板提供了一个异步 Action 的简单示例：\n\n```JS\n/* /store/counter/action.js */\nexport function asyncAdd() {\n  return dispatch =\u003e {\n    setTimeout(() =\u003e {\n      dispatch(add());\n    }, 2000);\n  };\n}\n\n// 组件中\n@connect(\n  ({ counter }) =\u003e ({\n    counter\n  }),\n  dispatch =\u003e ({\n    asyncAdd() {\n      dispatch(asyncAdd());\n    }\n  })\n)\nclass MyComponent extends Component {\n  render () {\n    return \u003cButton onClick={this.props.asyncAdd}\u003e点击 + 1\u003c/Button\u003e\n  }\n}\n```\n\n可以看到，异步 Action 和常规 Action 在使用上并没有任何区别。\n\n## API 封装\n\nTaro 已经封装了[网络请求](https://nervjs.github.io/taro/docs/native-api.html)，支持 Promise 化使用。本项目对`Taro.request()`进一步封装，以便统一管理接口、根据不同环境选择不同域名、设置请求拦截器、响应拦截器等。完整代码见 [/src/service](https://github.com/imageslr/taro-library/tree/master/src/service) 文件夹。\n\n### 域名切换\n\n生产环境使用线上接口，开发环境使用本地接口。新建`/service/config.js`文件：\n\n```JS\nexport default BASE_URL =\n  process.env.NODE_ENV === \"development\"\n    ? \"http://localhost:3000\" // 开发环境，需要开启mock server（执行：gulp mock）\n    : \"TODO\"; // 生产环境，线上服务器\n```\n\n### 封装请求\n\n代码见 [/src/service/api.js](https://github.com/imageslr/taro-library/tree/master/src/service/api.js)，代码非常简单。访问后台所需要的认证信息（token）可以添加在`option.header`中。\n\n### 添加拦截器\n\nTaro 支持添加[拦截器](https://nervjs.github.io/taro/docs/native-api.html#%E6%8B%A6%E6%88%AA%E5%99%A8)，可以使用拦截器在请求发出前后做一些额外操作。\n\n为什么要用拦截器呢？设想一下网络请求的场景。我们的目的是发出一个网络请求并接收响应，但是在发出请求之前，我们可能需要检查数据、添加用户的权限信息；如果项目大一些，我们可能还需要在发出请求之前先上报统计数据。这一系列流程之后才能真正执行我们的目标操作：网络请求。而获取到服务器响应后，我们还需要根据状态码执行不同的操作：401/403 跳转到登录页面，404 跳转到空白页面，500 展示错误信息...\n\n可以看到，如果将这些流程的代码都写到一起，那么代码将又长又乱，十分复杂。\n\n我们可以使用拦截器来解决这个问题。拦截器就是中间件，可以帮助我们优雅地分离业务逻辑。我们将每一个业务逻辑写成一个拦截器，在每个拦截器中，只需要关注当前阶段的代码实现。\n\n中间件的处理流程又称为洋葱模型，其执行过程是：先从最外层中间件从外到内依次执行到核心程序，再从核心程序从内到外依次执行到最外层中间件，每一个中间件的执行参数均是前一个中间件的返回值。如下图所示：\n\n![](./assets/中间件.png)\n\n下面是一个简单的中间件/拦截器示例代码：\n\n```JS\n/**\n * @param {object} req request对象\n * @param {function} next 调用下一个中间件的函数\n */\nfunction interceptor(req, next) {\n  // 在下一个中间件执行之前做一些操作...\n  // 比如添加一个参数\n  req.token = 'token'\n\n  // 执行下一个中间件...\n  // 保存其返回值\n  var res = next(req)\n\n  // 在下一个中间件返回结果之后做一些操作...\n  // 比如判断服务器返回的状态码\n  if(res.status == 401){\n    // ...\n  }\n  return res\n}\n\n```\n\n而`Taro.request`的拦截器函数与上例略有不同，将拦截器的调用方法改为了异步的形式：\n\n```js\n/**\n * @param {object} chain.requestParmas request对象\n * @param {function} chain.proceed  调用下一个中间件的函数\n */\nfunction interceptor(chain) {\n  // 在下一个中间件执行之前做一些操作...\n  // 比如添加一个参数\n  var requestParmas = chain.requestParmas;\n  requestParmas.token = \"token\";\n\n  // 执行下一个中间件...\n  return chain.proceed(requestParmas).then(res =\u003e {\n    // 在下一层行动返回结果之后做一些操作...\n    // 比如判断服务器返回的状态码\n    if (res.status == 401) {\n      // ...\n    }\n    return res;\n  });\n}\n```\n\n采用拦截器有利于代码解耦，符合高内聚低耦合的原则。本项目将拦截器定义在一个单独的文件中，以数组形式统一导出。使用 Taro 内置拦截器`Taro.interceptors.logInterceptor`打印请求的相关信息。代码见 [/src/service/interceptors.js](https://github.com/imageslr/taro-library/tree/master/src/service/interceptors.js)。\n\n### async 和 await\n\n最后，当我们发起网络请求时，可以使用 ES6 的`async/await`语法代替 Promise 对象，能大大提高代码的可读性。关于 async 和 await 的原理，可以查看[理解 JavaScript 的 async/await](https://segmentfault.com/a/1190000007535316)。\n\n一个简单示例：\n\n```JS\n// API.get() 返回一个 Promise 对象\n// Promise 方法调用\nfunction getBook(id) {\n  API.get(`/books/${id}`).then(res =\u003e {\n    this.setState({book: res});\n  }).catch(e =\u003e {\n    console.error(e);\n  })\n}\n\n// async/await 语法调用\nasync function getBook(id) {\n  try {\n    const book = API.get(`/books/${id}`);\n    this.setState({book: res});\n  } catch(e) {\n    console.error(e)\n  }\n}\n```\n\n## 搭建本地 mock 服务\n\n常见的 mock 平台有 EasyMock、rap2 等，不过这些网站有时候响应较慢，调试起来也不太方便，因此在本地搭建一个 mock 服务器是更好的选择。\n\n搭建本地 mock 服务器有几种思路，如本地安装 EasyMock，或者 php 简单写几行返回数据的代码，但是这些都需要安装额外的运行环境，工作量较大。这里我选择了 [simplest-mock-server](https://github.com/imageslr/simplest-mock-server)，一个开箱即用的 RESTful API mock 服务，详细使用教程见[文档](https://github.com/imageslr/simplest-mock-server)。\n\n启动 mock 服务的方法如下：\n\n```\n# 在本项目根目录下执行\ncd simplest-mock-server\n\n# 安装依赖\nnpm install\n\n# 启动 mock 服务\ngulp mock\n```\n\n关闭`gulp mock`终端进程，模拟网络中断场景；修改 [/simplest-mock-server/server.js](https://github.com/imageslr/taro-library/tree/master/simplest-mock-server/server.js) 中的延迟时长，模拟 timeout 场景。\n\n接口配置位于 [/simplest-mock-server/example](https://github.com/imageslr/taro-library/tree/master/simplest-mock-server/example)，改动此文件夹下的任何内容，均会实时刷新 mock 服务器。\n\n之后就享受愉快的开发过程吧！\n\n## 搭建云托管 mock 服务\n\n本项目的线上版本使用微信云托管提供 mock 接口。需要升级 taro 到 v3.0，代码改动较大，因此单独放在 [cloud](https://github.com/imageslr/taro-library/tree/cloud) 分支。微信云托管的搭建教程请查看 [weapp-library](https://github.com/imageslr/weapp-library#%E6%90%AD%E5%BB%BA%E4%BA%91%E6%89%98%E7%AE%A1-mock-server)。\n\n## 其他补充\n\n### Taro JSX\n\n不能在`render()`以外的函数中返回 JSX，也就是说下面这种写法是不允许的：\n\n```jsx\nrenderA() {\n  return \u003cView\u003eA\u003c/View\u003e\n}\n\nrenderB() {\n  return \u003cView\u003eB\u003c/View\u003e\n}\n\nrender () {\n  return (\n    \u003cView\u003e\n      {someCondition1 \u0026\u0026 this.renderA()}\n      {someCondition2 \u0026\u0026 this.renderB()}\n    \u003c/View\u003e\n  )\n}\n```\n\n### Taro 生命周期\n\nTaro 编译到小程序端后，每个组件的`constructor`首先会被调用一次（即使没有实例化），见[Taro 文档](https://nervjs.github.io/taro/docs/best-practice.html#%E7%BB%84%E4%BB%B6%E7%9A%84-constructor-%E4%B8%8E-render-%E6%8F%90%E5%89%8D%E8%B0%83%E7%94%A8)。\n\n在`constructor`中初始化`state`，在`componentDidMount`中发起网络请求，`componentWillMount`不知道有什么用。更多有关生命周期的知识，请查看[ Taro 文档](https://nervjs.github.io/taro/docs/component.html#componentwillmount)与[ React 组件生命周期](https://github.com/superman66/front-end-blog/issues/2)。\n\n### 运行配置相关\n\n#### 允许在 sass 中通过别名引入其他 sass 文件\n\n在 sass 中通过别名（@ 或 ~）引用其他 sass 文件，有[两个解决方法](https://github.com/NervJS/taro/issues/2478)：\n\n1. 在 js 中用`import '~taro-ui/dist/style/index.scss'`引入\n2. 增加 sass 的 importer 配置，可参考 https://github.com/js-newbee/taro-yanxuan/blob/master/config/index.js\n\n本项目采用的是第二种方法。\n\n#### 引入 iconfont 图标\n\n参考[ Taro UI 文档](https://taro-ui.aotu.io/#/docs/icon)\n\n- 步骤一：修改[ /config/dev.js ](https://github.com/imageslr/taro-library/tree/master/config/dev.js)与[ /config/prod.js ](https://github.com/imageslr/taro-library/tree/master/config/prod.js)\n- 步骤二：下载 iconfont 图标，保存在[ /src/assets/fonts](https://github.com/imageslr/taro-library/tree/master/src/assets/fonts)；编写字体图标库 css，代码见[ /src/styles/common/icon.scss ](https://github.com/imageslr/taro-library/tree/master/src/styles/common/icon.scss)\n- 步骤三：在`app.js`中全局引入`icon.scss`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimageslr%2Ftaro-library","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimageslr%2Ftaro-library","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimageslr%2Ftaro-library/lists"}