在美团实习的时候, 第一次接触到 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
来源: https://juejin.im/post/5af06fbf51882567161a803b