200行代碼寫一個簡易的dva

NO IMAGE

在美團實習的時候,第一次接觸到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);
})

控制檯的輸出

200行代碼寫一個簡易的dva

一點留言

這個當然是最粗糙的dva部分實現了,因為本身自己並沒有去看dva源碼,只是看了dva API 矇頭實現下,其中已經有很多很優秀的redux周邊生態,例如redux-thunk,logger等。當然也是複習了一下部分redux源碼了,當作自己學習的一個階段學習吧,最後像dva作者 陳謙 致敬。

最後留個地址吧:

http://oymaq4uai.bkt.clouddn.com/index.js

相關文章

JAVA併發之多線程基礎(3)

JAVA併發之多線程基礎(2)

JAVA併發之多線程基礎(1)

寫一個簡易中間件