redux存在的問題
-
專案中redux的樣板檔案太分散,書寫和維護都比較麻煩
-
使用thunk來處理非同步操作,不是那麼直觀
方案對比
基於redux資料流的管理方案:Dva
、mirror
和rematch
Dva
Dva是螞蟻金服開源的一個資料流管理方案,基於redux和redux-saga,簡化了開發體驗。Dva是一攬子的解決方案,可以使用侵入性很強的dva-cli來快速搭建專案,提供了路由層面的適配;也可以使用dva-core來引入核心的程式碼,減少侵入性。
缺點
-
如果使用Dva的一整套框架,現有的專案會有較大的改動
-
Dva使用redux-saga來處理非同步,學習成本比較高
類似於Dva的一個redux資料流方案,最新一次更新在兩個月之前,一直沒有釋出1.0的版本
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