200行程式碼寫一個簡易的dva

FounderIsShadowWalker發表於2018-05-07

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

相關文章