Taro-library:Taro + Redux + 本地 Mock Server 示例項目

項目簡介

項目地址:imageslr/taro-libraryphp

本項目是在線借書平臺小程序使用 Taro 重構後的版本,僅包含三個示例頁面,很是簡單。面向人羣主要是 Taro/React/Redux 的初學者,目的是提供一個簡單的實踐項目,幫助理解 Taro 與 Redux 的配合方式與 Taro 的基本使用。本項目還提供了一個快速搭建本地 mock 服務的解決方案。css

由於我也是剛接觸 Taro/React,因此只是分享一些開發經驗,繞開一些小坑。若是以爲不錯的話,請點右上角「⭐️Star」支持一下我,謝謝!若是有問題,歡迎提 issue;若是有任何改進,也歡迎 PR。html

掃碼體驗:
前端

code

技術棧

Taro + Taro UI + Redux + Webpack + ES6 + Mocknode

項目截圖

UI

運行項目

本項目在如下環境中編譯經過:taro v1.2.20、nodejs v8.11.二、gulp v3.9.一、微信開發者工具最新版react

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

$ cd taro-library

$ npm install 或者 yarn

$ npm run dev:weapp

// 新建一個終端,在項目根目錄下執行
$ gulp mock
複製代碼

開始學習

Taro 簡介

Taro 是一個遵循 React 語法規範的多端開發解決方案。最近想學習 React,因而就想到使用 Taro 重構很早以前開發的在線借書平臺小程序。雖然 Taro 上手有必定難度,可是其 React 框架比小程序原生更爲靈活與規範,給我帶來了非凡的開發體驗。git

在正式開始以前,您必須對 Taro 框架、 React 語法與小程序框架有必定的瞭解。此外,我建議您閱讀如下文檔,會更容易上手:github

  • 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 插件npm

VS Code 對 JSX 與 TypeScript 有自然的支持,使用 VS Code 開發 Taro,不須要配置任何插件就能實現 Taro 組件的自動 import 與 props 提示,很是方便。json

代碼格式化插件我選擇 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 && npm install json-server mockjs gulp gulp-nodemon browser-sync --save-dev
複製代碼

引入 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 簡單寫幾行返回數據的代碼,可是這些都須要安裝額外的運行環境,工做量較大。因此我選擇 json-server 實現 mock 服務,搭建過程主要參考了純手工打造前端後端分離項目中的 mock-server

json-server 是一個開箱即用的 REST API 模擬工具,它的文檔中有一些簡單示例。不過json-server還沒法知足我對 mock 服務器的所有需求,因此後面還須要對它進行一些配置。

完整代碼見 /mock

安裝依賴

這裏須要安裝幾個依賴包,以前安裝過就不用再裝了:

$ npm install json-server mockjs gulp gulp-nodemon browser-sync --save-dev
複製代碼

要注意 gulp 須要是 3.9.* 版本。後續編譯小程序或者啓動 mock 服務器時若是報錯,再運行一遍npm install就行了。

設置 json-server

└── mock
    ├── factory
    │   └── book.js
    ├── db.js
    ├── routes.js
    └── server.js
複製代碼

首先使用 Mock.js 生成一些模擬數據。這部分代碼見 /mock/factory/book.js,Mock.js 的使用方式請查看文檔

而後建立 mock 數據源,代碼見 /mock/db.jsjson-server會將數據源中的鍵名做爲接口路徑名,做爲接口返回的數據。

json-server不支持在數據源的鍵名中添加/,沒法直接設置/books/new這樣的二級路徑,所以咱們須要使用json-server提供的路由重寫功能:在數據源中,使用books-new表示books/new;在路由表中,將/books/new指向/books-new。代碼見 /mock/routes.js

最後在 /mock/server.js 中添加兩個中間件。第一個是將全部的POST請求轉爲GET請求,防止數據被修改;第二個是爲服務器設置一個 750ms 的延遲,模擬更真實的加載過程:

// 將 POST 請求轉爲 GET
server.use((request, res, next) => {
  request.method = "GET";
  next();
});

// 添加一個750ms的延遲
server.use((request, res, next) => {
  setTimeout(next, 750);
});
複製代碼

啓動服務

在項目根目錄下執行gulp mock便可啓動 mock 服務器,以後改動/mock文件夾的任何內容,均會實時刷新 mock 服務器。代碼見 /gulpfile.js

開發時,首先執行以下命令,編譯小程序:

$ npm run dev:weapp
複製代碼

而後新建一個終端,執行如下命令,啓動 mock 服務器:

$ gulp mock
複製代碼

以後就享受愉快的開發過程吧!

補充說明

  1. 關閉gulp mock終端進程,模擬網絡中斷場景;修改 /mock/server.js 中的延遲時長,模擬 timeout 場景。
  2. mock 服務器只能在電腦訪問,若是想在真機上測試,可使用 EasyMock:
    1. 啓動 mock 服務器,訪問localhost:3000,能夠看到全部 mock 接口
    2. 在 EasyMock 項目中新建接口,將 mock 接口的模擬數據複製過去
    3. /src/service/config.js 中的開發環境BASE_URL改成 EasyMock 項目的BASE_URL
  3. 參考資料:json-server 文檔純手工打造前端後端分離項目中的 mock-server

其餘補充

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 配置,可參考 github.com/js-newbee/t…

本項目採用的是第二種方法。

引入 iconfont 圖標

參考 Taro UI 文檔

相關文章
相關標籤/搜索