1. 程式人生 > >redux探索:rematch

redux探索:rematch

redux存在的問題

  • 專案中redux的樣板檔案太分散,書寫和維護都比較麻煩

  • 使用thunk來處理非同步操作,不是那麼直觀

方案對比

基於redux資料流的管理方案:Dvamirrorrematch

Dva

Dva是螞蟻金服開源的一個數據流管理方案,基於redux和redux-saga,簡化了開發體驗。Dva是一攬子的解決方案,可以使用侵入性很強的dva-cli來快速搭建專案,提供了路由層面的適配;也可以使用dva-core來引入核心的程式碼,減少侵入性。

缺點
  • 如果使用Dva的一整套框架,現有的專案會有較大的改動

  • Dva使用redux-saga來處理非同步,學習成本比較高

mirror

類似於Dva的一個redux資料流方案,最新一次更新在兩個月之前,一直沒有釋出1.0的版本

rematch

rematch的靈感來自於Dva和mirror,將兩者的有點結合了起來。

優點
  • 使用了類似Dva的model檔案結構,統一管理同步和非同步操作

  • 通過中間鍵實現了async/await的方式來處理非同步,捨棄了Dva中的redux-saga

  • 提供了redux的配置項,可以相容專案中的老程式碼

  • 支援多個store

缺點?
  • 將model中reducers和effects的方法掛載在dispatch函式上,造成dispatch既是一個函式,又是一個物件


Rematch Mirror Dva
適用框架 所有框架 / 不使用框架 React React
適用路由 所有路由 / 不使用路由 RR4 RR3, RR4 / 不使用路由
移動端 ×
開發者工具 Redux, Reactotron Redux Redux
外掛化
reducers
effects async/await async/await redux saga
effect params (payload, internals) (action, state) (action, state)
監聽方式 subscriptions hooks subscriptions
懶載入模型
鏈式 dispatch
直接 dispatch
dispatch promises
載入外掛
persist plugin
package size 14.9k(gzipped: 5.1k)|| redux + thunk: 6k(2k) 130.4k(gzipped: 33.8k) dva-core: 72.6k(gzipped: 22.5k)

rematch

API

  import { init } from '@rematch/core';
  ​
  const store = init({
    models: {
      count: {
        state: 0,
        reducers: {
          add: (state, payload) => state + payload,
          del: (state, payload) => state - payload,
          'otherModel/actionName': (state, payload) => state + payload,
        },
        effets: {
          async loadData(payload, rootState) {
            const response = await fetch('http://example.com/data')
            const data = await response.json()
            this.add(data)
          }
        }
      }
      list: {}
    },
    redux: {
      reducers: {},
      middlewares: [thunk],
    },
    plugins: [loading]
  })複製程式碼

init

對rematch進行初始化,返回一個store物件,包含了使用redux初始化store物件的所有欄位。

models: { [string]: model }

一個物件,屬性的鍵作為rootState上的的鍵

model.state: any

用來初始化model

model.reducers: { [string]: (state, payload) => any }

一個物件,屬性是用來改變model state的方法,第一個引數是這個model的上一個state,第二個引數是payload,函式返回model下一個state。這些方法應該是純函式。

model.effects: { [string]: (payload, rootState) }

一個物件,非同步或者非純函式的方法放在這個物件中,可以與async/await一起使用

redux

通過這個屬性,可以相容老專案中的redux配置。

plugins

rematch是一個外掛系統,通過這個欄位可以配置第三方的外掛。

redux流程:


rematch流程:

例子

  ##index.js
  import React from 'react'
  import ReactDOM from 'react-dom'
  import { Provider } from 'react-redux'
  import { init } from '@rematch/core'
  import App from './App'
  ​
  const count = {
    state: 0,
    reducers: {
      increment: s => s + 1,
    },
    effects: dispatch => ({
      async asyncIncrement() {
        await new Promise(resolve => {
          setTimeout(resolve, 1000)
        })
        dispatch.count.increment()
      },
    }),
  }
  ​
  const store = init({
    count,
  })
  ​
  // Use react-redux's <Provider /> and pass it the store.
  ReactDOM.render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById('root')
  )
  ​
  ​
  ##App.js
  import React from 'react'
  import { connect } from 'react-redux'
  ​
  // Make a presentational component.
  // It knows nothing about redux or rematch.
  const App = ({ count, asyncIncrement, increment }) => (
    <div>
      <h2>
        count is <b style={{ backgroundColor: '#ccc' }}>{count}</b>
      </h2>
  ​
      <h2>
        <button onClick={increment}>Increment count</button>{' '}
        <em style={{ backgroundColor: 'yellow' }}>(normal dispatch)</em>
      </h2>
  ​
      <h2>
        <button onClick={asyncIncrement}>
          Increment count (delayed 1 second)
        </button>{' '}
        <em style={{ backgroundColor: 'yellow' }}>(an async effect!!!)</em>
      </h2>
    </div>
  )
  ​
  const mapState = state => ({
    count: state.count,
  })
  ​
  const mapDispatch = dispatch => ({
    increment: dispatch.count.increment,
    asyncIncrement: dispatch.count.asyncIncrement,
  })
  ​
  // Use react-redux's connect
  export default connect(
    mapState,
    mapDispatch
  )(App)
  ​複製程式碼

老專案接入

主要針對已經使用thunk中間鍵的老專案。

1、安裝依賴,並刪除依賴中的redux

yarn add @rematch/core

yarn remove redux (刪除redux可能會造成eslint報錯)

2、修改redux入口檔案

  src/store/index.js
  ​
  import { init } from '@rematch/core';
  import thunk from 'redux-thunk';
  import reduxReducerConfig from '@/reducers';
  import models from '../models';
  ​
  const store = init({
    models,
    redux: {
      reducers: {
        ...reduxReducerConfig
      },
      middlewares: [thunk],
    },
  });
  ​
  export default store;複製程式碼

3、修改reducers的入口檔案

  import { routerReducer as routing } from 'react-router-redux';
  - import { combineReducers } from 'redux';
  import dispatchConfigReducer from './dispatch-config';
  import counterReducer from './count';
  ​
  - export default combineReducers({
  -   routing,
  -   dispatchConfigReducer,
  -   counterReducer,
  - });
  ​
  + export default {
  +   routing,
  +   dispatchConfigReducer,
  +   counterReducer,
  + };複製程式碼

4、增加model的入口檔案

  + src/models
  + src/models/re-count.js
  + src/models/config-list.js
  + src/models/index.js
  ​
  index.js
  ​
  import reCount from './re-count';
  import configList from './config-list';
  ​
  export default {
    reCount,
    configList,
  };複製程式碼

如果老專案中沒有使用redux,可以使用yarn remove thunk刪除thunk的依賴和reducers這個資料夾,並且在init初始化的時候可以不用傳redux這個配置。

如果接入rematch,需要鎖定版本,rematch中引入的redux版本為4.0.0,所以老專案中的redux要更新為為4.0.0,不然打包的時候會把兩個版本的redux都打進去。

新專案配置

  index.js
  ​
  import React from 'react';
  import { render } from 'react-dom';
  import { browserHistory, Router } from 'react-router';
  import { syncHistoryWithStore } from 'react-router-redux';
  import { Provider } from 'react-redux';
  import routes from '@/routes';
  import store from '@/store';
  import '@/styles/index.less';
  ​
  const history = syncHistoryWithStore(browserHistory, store);
  ​
  render(
    <Provider store={store}>
      <Router history={history} routes={routes} />
    </Provider>,
    document.getElementById('root'),
  );
  ​
  ---------------------------------------------------------------------------------------
  ​
  新建store資料夾,並新增index.js
  ​
  import { init } from '@rematch/core';
  import { routerReducer as routing } from 'react-router-redux';
  import models from '../models';
  ​
  const store = init({
    models,
    redux: {
      reducers: {
        routing,
      },
    },
  });
  ​
  export default store;
  ​
  ---------------------------------------------------------------------------------------
  ​
  新建models資料夾,並新增index
  ​
  models結構
  ├── common
  │   ├── bizLineList.js
  │   └── index.js
  └── index.js複製程式碼

bug

Redux DevTools 要升級到最新版,2.16.0有bug

參考

重新思考Redux

Rematch: 重新設計 Redux

精讀《重新思考 Redux》