手写Redux核心功能

224 阅读2分钟

简介

Redux的核心API有combineReducers, createStore, applyMiddleware, bindActionCreators、composebindActionCreators目前没实现,稍后补上。

先附上actions和actionType

// actionType/constant.js
export const ADD_TODO = 'ADD_TODO';
export const MODIFY_TODO = 'MODIFY_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

// actions/todo.js
import * as constant from '../actionType/constant';

export default {
  addTodo: text => {
    return {
      type: constant.ADD_TODO,
      id: parseInt(Math.random().toString().slice(-5)),
      text
    }
  },
  modifyTodo: (id, text) => {
    return {
      type: constant.MODIFY_TODO,
      id,
      text
    }
  },
};


// actions/index.js
import todo from './todo';
import visiblilityFilter from './visiblilityFilter';

export default {
  todo,
  visiblilityFilter,
}

createStore

createStore就是创建store的函数,第一个参数reduce必传,initalState和enhance可选,这里是简单实现,没做错误处理,实际上当第二个参数是函数时会把它当做enhance,而initalState是undefined,具体可参 Redux-createStore 82-85行 。

createStore返回一个对象,有几个属性getState(获取state)、dispatch(派发action,reducer更新state)、subscribe(将订阅的回调加入listener列表,每次reducer更新state后会执行listener中的回调),他们是函数。

bug:自己实现的createStore,不传initalState,state默认空对象,而Redux源码中,即使不传initalState,createStore后得到的store不是空对象。需要fix。

// lib/redux/createStore
export default function createStore(reducer, initalState, enhance) {
  let state = initalState || {},
    listeners = []
  const getState = () => {
    return state
  }
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach(fn => fn())
  }
  const subscribe = (fn) => {
    listeners.push(fn)
    return () => {
      unsubscribe(fn)
    }
  }
  const unsubscribe = (listener) => {
    let index = listeners.indexOf(listener)
    if (index === -1) {
      listeners.splice(index, 1)
    }
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}

combineReducers

将多个reducer合并成一个,有点像Object.assign,下面的key可以理解为子模块的标识

// lib/redux/combineReducers.js
/* reducerObj相当于
*    {
*       todos,
*       visibilityFilter,
*    }
*/
export default function combineReducers(reducerObj) {
  return function (state = {}, action) {
    const keys = Object.keys(reducerObj);
    const newState = {}
    keys.forEach(key => {
      // key相当于todos,reducer相当于reducers/todos.js中的reducer
      const reducer = reducerObj[key]
      newState[key] = reducer(state[key], action)
    })
    return {
      ...state,
      ...newState
    }
  }
}

// reducers/index.js 
// import { combineReducers } from 'redux'
import { combineReducers } from '../lib/redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

const todoApp = combineReducers({
  todos,
  visibilityFilter,
})

export default todoApp

createReducer

为了解决reducer中Switch分支而产生,接受initialState和handlers对象,返回一个reducer

// lib/redux/createReducer
const createReducer = (initialState, handlers) => {
  return (state = initialState, action) => {
      if (handlers.hasOwnProperty(action.type)) {
          return handlers[action.type](state, action);
      }
      return state;
  };
};
export default createReducer;

applyMiddleware

为了记录派发action、执行reducer前后state,可以用middleware来增强dispatch(为什么是dispatch?因为action就是纯对象,reducer是纯函数,subscribe就是添加订阅回调到队列,没法搞事情啊!!!)。增强dispatch,可以修改dispatch,比如每次dispatch打印action。

const __dispatch = store.dispatch
store.dispatch = (action) => {
 	console.log('action: ', action);
  __dispatch(action);
} 

想想每次使用middleware都要保存旧的dispatch再重写,不太方便,先将middleware重新定义如下:

const middleware =  (store) => (dispatch) => (action) => {
   // do what you want
  dispatch(action);
  // do what you want
};

两个简单的middleware:

// 只打印出 Action
const loggerAction = (store) => (dispatch) => (action) => {
  console.log('action: ', action);
  dispatch(action);
};

// 只打印出 更新后的state
const loggerState = (store) => (dispatch) => (action) => {
  console.log('current state: ', store.getState());
  dispatch(action);
  console.log('next state: ', store.getState());
};

现在来实现applyMiddleware。applyMiddleware就是将多个middleware串起来,forEach就搞定了。

PS:applyMiddleware有不同形式,这里只实现了 store = applyMiddleware(store, funcArr)

// lib/redux/applyMiddleware
const applyMiddleware = (store, middlewares) => {
  let dispatch = store.dispatch;
  middlewares.forEach((middleware) => {
      dispatch = middleware(store)(dispatch);
  });

  return {
      ...store,
      dispatch,
  };
};

export default applyMiddleware;

// src/todoDemo/index.js 
// 每次dispatch会先打印current state 和 action,执行reducer后打印next state,参考Koa洋葱圈模型
store = applyMiddleware(store, [loggerAction, loggerState]);

compose

给定函数列表,将多个函数有序执行

// lib/redux/compose
const compose = (...funcArr) => {
  return funcArr.reduce((a, b) => (...args) => a(b(...args)));
};

export default compose;