redux 存在的问题
项目中 redux 的样板文件太分散, 书写和维护都比较麻烦
使用 thunk 来处理异步操作, 不是那么直观
方案对比
基于 redux 数据流的管理方案: Dva,mirror 和 rematch
Dva
Dva 是蚂蚁金服开源的一个数据流管理方案, 基于 redux 和 redux-saga, 简化了开发体验. Dva 是一揽子的解决方案, 可以使用侵入性很强的 dva-cli 来快速搭建项目, 提供了路由层面的适配; 也可以使用 dva-core 来引入核心的代码, 减少侵入性.
缺点
如果使用 Dva 的一整套框架, 现有的项目会有较大的改动
Dva 使用 redux-saga 来处理异步, 学习成本比较高
https://github.com/mirrorjs/mirror
类似于 Dva 的一个 redux 数据流方案, 最新一次更新在两个月之前, 一直没有发布 1.0 的版本
https://github.com/rematch/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 https://rematch.gitbook.io/handbook/
Rematch: 重新设计 Redux https://zhuanlan.zhihu.com/p/34199586
精读《重新思考 Redux》 https://zhuanlan.zhihu.com/p/36810237
来源: https://juejin.im/post/5bfff1b7e51d45517b0ce5a7