優雅的redux非同步中介軟體 redux-effect
不吹不黑,redux蠻好用。只是有時略顯繁瑣,叫我定義每一個action、action type、使用時還要在元件上繫結一遍,臣妾做不到呀!下面分享一種個人比較傾向的極簡寫法,仍有待完善,望討論。
github:github.com/liumin1128/…
基於redux、async/await、無侵入、相容性良好的非同步狀態管理器。
install
npm i -S redux-effect // or yarn add redux-effect 複製程式碼
use
import { createStore, applyMiddleware, combineReducers } from 'redux'; import { reduxReduers, reduxEffects } from 'redux-effect'; const models = [ test1, test2, ...]; const reducers = combineReducers(reduxReduers(models)); const middlewares = [ reduxEffects(models) ]; const store = createStore( reducers, initialState, middlewares ); 複製程式碼
從程式碼可以看出,從reduxReduers, reduxEffects中得到的就是標準的reducer和middleware,完美相容其他redux外掛,也可以輕鬆整合進老專案中。
完整例子:example
model
在redux-effect中,沒有action的概念,也不需要定義action type。
所有關於某個state的一切宣告在一個model中,本質就是一個物件。
export default { namespace: 'test', state: { text: 'hi!' }, reducers: { save: (state, { payload }) => ({ ...state, ...payload }), clear: () => ({}) }, effects: { fetch: async ({ getState,dispatch }, { payload }) => { await sleep(3000); await dispatch({ type: 'test/clear' }); await sleep(3000); await dispatch({ type: 'test/save', payload: { text: 'hello world' } }); } } }; 複製程式碼
namespace:
model的名稱空間,對應state的名字,必填,只接受一個字串。
state:
state的初始值,非必填,預設為空物件
reducers:
必填,相當於同步執行的action方法,接受兩個引數state和action,合併後返回新的state狀態值。
effects:
非必填,相當於非同步執行的action方法,接受兩個引數store和action,store裡包括redux自帶的getState和dispatch方法,action為使用者dispatch時帶的引數。
dispatch
這裡的dispatch就是redux中的dispatch,但有幾個約定。
- 不傳定義好的action,而是直接傳一個普通物件。
- type的組織形式:namespace + '/' + reducer或effect方法名
- 引數的傳遞:需要合併的引數用payload包裹
定義每一個action,並將其繫結到檢視層過於繁瑣,去action化則讓事件的觸發變的靈活。
普通事件
傳送事件時,不區分同步還是非同步,只管dispatch,一切都已在model中定義好。
// 同步 dispatch({ type: 'test/save', payload: { text: "hello world" } }) // 非同步 dispatch({ type: 'test/fetch' }) 複製程式碼
等待
等待一個事件完成再執行邏輯,dispatch方法是可以被await的,十分輕鬆。
async function test() { await dispatch({ type: 'test/fetch' }) await console.log('hello world') } 複製程式碼
回撥
等待某個事件,再執行外部定義的某個回撥函式,只需要在action欄位里加上callback方法,在effect中呼叫即可。
相比較await,回撥可以拿到某些返回值,也可以在effect流程的中間部分執行。
dispatch({ type: 'test/fetch', callback1, callback2 }) { effects: { fetch: async ({ getState,dispatch }, { payload, callback, callback2 }) => { const state = await getState() await sleep(3000); await callback1(state) await sleep(3000); await callback2(state) } } } 複製程式碼
自定義reducer
reducer其實就是redux中的reducer,用法完全一樣。比如定義一個push方法,將後續資料,壓入到原有資料後面,可以這樣寫。
export default { namespace: 'test', state: { data: [] }, reducers: { save: (state, { payload }) => ({ ...state, ...payload }), clear: () => ({}), push: (state, { payload = {} }) => { const { key = 'data', data } = payload; return { ...state, [key]: state[key].concat(data) }; } }, }; 複製程式碼
自定義effect
effect其實就是一個普通async函式,接受store和action兩個引數,可以使用async/await,可以執行任意非同步方法,可以隨時拿到state的值,可以dispatch觸發另一個effect或者reducer。
loading
也許你會想監聽某個effect,拿到loading狀態,以便在ui給使用者一個反饋。一般情況下監聽一個非同步方法,只需要在effect的開頭和結束,各自設定狀態即可,與常規寫法無異。
但這裡也提供一種model級別的loading狀態,新增一個名為loading的model,再使用reduxEffectsWithLoading包裹需要監聽的model即可。