在美團實習的時候,第一次接觸到dva這樣的react框架,回學校的時候,就想有機會自己實現一下這樣的框架,雖然自己水平有限,但是可以試一試哈。 目標是實現dva model的同步和非同步 dispatch action。
看看 dva 的構成
let counterModel = {
namespace: 'counter',
state: {
num: 0
}
reducers: {
add(state, action){
return {
num: state.num + 1
}
}
}
}
複製程式碼
對state的更新
var app = new dva();
app.model(counterModel);
app.start();
app._store.dispatch({
type: 'counter/add'
});
複製程式碼
上述就是 dva 對 state 的更新, 通過dispatch {type: A / B} 其中 A 是指 model的 namespace, B 是指 model 中 reducers 具體的reducer方法。
其中 dva 對非同步的處理是用 redux-saga 處理的,因為作者並不熟悉redux-saga,拿 redux-thunk 代替了。
好,我們開工了
-
第一步 建立store
const createStore = (reducer, initialState) => { let currentReducer = reducer; let currentState = initialState; let listener = () => { }; return { getState() { return currentState; }, dispatch(action) { let { type } = action; currentState = currentReducer(currentState, action); listener(); return action; }, subscribe(newListener) { listener = newListener; } } } 複製程式碼
store 主要是 儲存資料,開放state更新介面。
-
第二步 引入中介軟體 applyMiddleware
const compose = (...funcs) => { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)); } const applyMiddleware = (...middlewares) => { return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => store.dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } 複製程式碼
redux 中介軟體 洋蔥模型,修改了dispatch 方法。
-
引入非同步中介軟體redux-thunk和logger中介軟體
const logger = store => next => action => { console.log('prevState', store.getState()); let result = next(action); console.log('nextState', store.getState()); return result; }; const thunk = ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); } 複製程式碼
這裡引入 redux-thunk 做非同步處理。
-
加入測試model
let counterModel = { namespace: 'counter', state: { num: 0 }, reducers: { add(state, action) { console.log('reducer add executed'); return { num: state.num + 1 } }, asyncAdd(state, action) { console.log('reducer asyncAdd executed'); return { num: state.num + 1 } }, test(state, action) { console.log('reducer test executed'); return { state } } } }; let userModel = { namespace: 'user', state: { name: 'xxxx' }, reducers: { modify(state, { payload }) { console.log('reducer modify executed'); let { name } = payload return { name } } } }; 複製程式碼
-
對不同model下的reducer進行分發
const combineReducer = (reducers) => (state = {}, action) => { let { type } = action; let stateKey = type.split('/')[0]; let reducer = type.split('/')[1]; reducers.map((current) => { if (current.name === reducer) { state[stateKey] = current(state[stateKey], action); } }); return state; } 複製程式碼
這裡因為 combineReducer 是 reducer的總入口,在這裡根據action 的 type 轉發到具體model下的reducer方法
-
dva 建構函式
class dva { constructor() { this._models = []; this._reducers = []; this._states = {}; } model(model) { this._models.push(model); } start() { for (var i = 0; i < this._models.length; i++) { this._states[this._models[i].namespace] = { ...this._models[i].state }; Object.keys(this._models[i].reducers).map((key) => { if (this._models[i].reducers.hasOwnProperty(key)) { this._reducers.push(this._models[i].reducers[key]); } }) } var rootReducer = combineReducer(this._reducers); let createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore); this._store = createStoreWithMiddleware(rootReducer, this._states); this._store.subscribe(() => { console.log(this._store.getState()); }) } } 複製程式碼
dva 構造方法主要工作是快取model,建立store。
測試資料
var app = new dva();
app.model(counterModel);
app.model(userModel);
app.start();
app._store.dispatch({
type: 'counter/add'
});
app._store.dispatch({
type: 'user/modify',
payload: {
name: 'shadow'
}
})
app._store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({
type: 'counter/asyncAdd'
})
}, 5000);
})
複製程式碼
控制檯的輸出
一點留言
這個當然是最粗糙的dva部分實現了,因為本身自己並沒有去看dva原始碼,只是看了dva API 矇頭實現下,其中已經有很多很優秀的redux周邊生態,例如redux-thunk,logger等。當然也是複習了一下部分redux原始碼了,當作自己學習的一個階段學習吧,最後像dva作者 陳謙 致敬。
最後留個地址吧:
http://oymaq4uai.bkt.clouddn.com/index.js