
|-src|-utils|-actionType.js //|-formatProdErrorMessage.js // 不写|-isPlainObject.js // 判断是否为纯对象|-kindOf.js // 用来判断类型|-symbol-observable.js // 不写|-warning.js // 负责控制台错误日志的输出,不写|-applyMiddleware.js // 我们使用它应用中间件时,它返回一个 enhancer 函数作为 createStore 的第三个参数|-bindActionCreator.js // 用得不多。把 action creators 转成拥有同名 keys 的对象,使用 dispatch 把每个 action creator 包装起来,这样可以直接调用它们。|-combineReducers.js // 将 reducers 组合起来|-compose.js // 函数组合,工具函数来的|-createStore.js // 创建 store|-index.js // 入口文件
github 仓库:https://github.com/Konsoue/redux-learn
手写 redux
- 我们从入口 index.js 开始写起。
 - 我们使用 redux 的时候肯定是要创建 store,那么就来看看 createStore 做了什么事情
 - 在讲函数式编程的时候,我们有了解过函数组合 compose,这里的 compose.js 就是这样
 - 在使用 redux 写多个 reducer 的时候,我们会最后用 combindReducer 把这些 reducer 合在一起
 - 应用中间件的时候,我们会使用 applyMiddleware,所以看看这个函数做了什么操作
 
index.js
import createStore from './createStore';import compose from './compose';import combineReducers from './combineReducers';import applyMiddleware from './applyMiddleware';import bindActionCreators from './bindActionCreators';export {createStore,compose,combineReducers,applyMiddleware,bindActionCreators}
createStore.js
import isPlainObject from "./utils/isPlainObject"import kindOf from "./utils/kindOf"/**** @param {Function} reducer* @param {*} preloadedState 初始的 state* @param {Function} enhancer 增强器,applyMiddleware 的返回值。* @returns {Store}*/function createStore(reducer, preloadedState, enhancer) {// 如果有中间件,将 createStore 交给中间件创建,并传入 reducer, preloadedStateif (kindOf(enhancer) !== 'undefined') {return enhancer(createStore)(reducer, preloadedState);}let currentReducer = reducerlet currentState = preloadedStatelet currentListeners = [] // 当前的 listeners ,即所有监听函数let nextListeners = currentListeners // 在 state 数据改变前,订阅的监听函数,先放在 nextListeners,以保证 currrentListeners 数据修改前的不变性。let isDispatching = false // reducers 正在执行的标志/*** 派发 action,这是改变 state 的唯一途径* @param {Object} action 一个纯对象,代表了将要改变什么。* @returns {Object} 为了方便,返回用户派发的 action*/const dispatch = (action) => {if (!isPlainObject(action)) {throw new Error(`Actions 必须是纯对象。而当前 Actions 的类型是${kindOf(action)}。你可以加中间件, 来处理特殊类型`)}if (!action.type) throw new Error('Actions 必须有一个 type 属性')if (isDispatching) throw new Error('还不可以给 reducers 派发 action')try {// Reducers 开始处理 actionisDispatching = truecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// 处理完 action, 触发监听事件// 先把 nextListeners 赋值给 currentListeners// 因为 subscribe 添加监听函数时,会将监听函数先放到 nextListeners// 以此来保证 currentListeners 触发前,自身的不变性const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action}/*** 将 currentListeners 浅拷贝后,赋给 nextListeners*/const ensureCanMutateNextListeners = () => {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()}}/*** 订阅:添加一个监听函数* @param {Function} listener 监听函数* @return {Function} 返回一个函数用于移除,这个监听函数*/const subscribe = (listener) => {if (isDispatching) throw new Error('reducer 正在执行, 你不能调用 store.subscribe()')let isSubscribed = true // 标志该 listener 是否已经订阅ensureCanMutateNextListeners()nextListeners.push(listener)// 返回一个取消订阅的函数return function unsubscribe() {if (!isSubscribed) returnif (isDispatching) throw new Error('reducer 正在执行, 你不能调用 unsubscribe()')isSubscribed = falseensureCanMutateNextListeners()const index = nextListeners.indexOf(listener)nextListeners.splice(index, 1)currentListeners = null // 这里赋值为 null,释放内存。不会影响数据改变后,调用监听函数的}}/*** 读取被 store 管理的 state 树* @returns {any} 在你的项目中,目前的 state 树*/const getState = () => {if (isDispatching) throw new Error('reducer 正在执行, 你不能调用 store.getState()')return currentState}/*** 替换 reducer* @param {Function} nextReducer*/const replaceReducer = (nextReducer) => {currentReducer = nextReducer}return {dispatch,subscribe,getState,replaceReducer,}}export default createStore
compose.js
const compose = (...fns) => {if (fns.length === 0) return (args) => argsif (fns.length === 1) return fns[0]fns.reduce((pre, cur) => (...args) => pre(cur(...args)))}export default compose
combindReducer.js
import kindOf from "./utils/kindOf"/*** 将外部传进来的 reducers 合成一个总的 reducers* @param {Object} reducers* @returns {Function}*/const combineReducers = (reducers) => {const reducerKeys = Object.keys(reducers)const resultReducer = (state = {}, action) => {const nextState = {}for (let i = 0; i < reducerKeys.length; i++) {const key = reducerKeys[i]const reducer = reducers[key]const previousStateForKey = state[key] // 拿到某个 reducer 处理前对应的 stateconst nextStateForKey = reducer(previousStateForKey, action) // 拿到处理后的 stateif (kindOf(nextStateForKey) === 'undefined') {throw new Error(`${key} reducer 处理结果不能是 undefined`)}nextState[key] = nextStateForKey}return nextState}return resultReducer}export default combineReducers
从源码 reducers[key] 和 state[key]可以看出,我们使用 redux 创建 reducer 和 state 的时候,key 需要一一对应。
import { combindReducer, createStore } from 'redux'const initalState = {home: {},login: {},user: {}}const homeReducer = (state, action) => {}const userReducer = (state, action) => {}const loginReducer = (state, action) => {}// reducers 的 key 要和 initalState 对应const reducers = {home: homeReducer,user: userReducer,login: loginReducer}const resultReducer = combineReducers(reducers)const store = createStore(reducers, initalState)
applyMiddleware.js
import compose from './compose'/*** 我们使用它应用中间件,使得中间件可以调用 redux 的 store 的方法** @param {...Function} middlewares* @returns {Function} 返回的函数,用于 createStore 的第三个参数*/const applyMiddleware = (...middlewares) => {const enhancer = (createStore) => {const newCreateStore = (reducers, preloadState) => {const store = createStore(reducers, preloadState)// chain = [ (next) => {}, (next) => {}, ...]// 其中的 next 方法是我们下面将要传入的 store.dispatchconst chain = middlewares.map(middleware => middleware(store))// 一系列中间件对 dispatch 的包裹,形成一个新的 dispatch。// 所以外界调用 store.dispatch 就是调用 newDispatchconst newDispatch = compose(...chain)(store.dispatch)return {...store,dispatch: newDispatch}}return newCreateStore}return enhancer}export default applyMiddleware
utils
isPlainObject.js
判断对象是否为纯对象(简单对象)。
纯对象:通过{}或 new Object()创建的都是纯对象。 
有的认为
Object.create(null)创建的对象也是纯对象。
/*** @param {any} obj* @returns {boolean} ture 表示 obj 是纯对象*/const isPlainObject = (obj) => {if (typeof obj !== 'object' || obj === null) return false;let proto = obj;while (Object.getPrototypeOf(proto) !== null) {proto = Object.getPrototypeOf(proto);}return Object.getPrototypeOf(obj) === proto}export default isPlainObject
为什么 redux 不直接判断 Object.prototype === Object.getPrototypeOf(obj)呢?
答:因为 redux 考虑了页面嵌入子页面的情况,而不同页面之间的内置对象都是独立创建出来的。
比如:Parent 页面,嵌入 Child 页面。Parent 的 Object 构造函数 !== Child 的 Object 构造函数。
如果直接使用题目的判断方式,那么如果在 Child 页面 let obj = new Object()。
在 Parent 页面的 redux 判断时,obj 的原型是 Child 的 Object 的原型,不等于 Parent 的 Object 的原型。
kindOf.js
redux 生产模式使用 typeof,开发模式自行封装了 miniKindOf 判断函数。
我们直接使用生产模式就好了。
const kindOf = (val) => typeof valexport default kindOf;
开发模式的方法,如下,有兴趣就看。
// Inlined / shortened version of `kindOf` from https://github.com/jonschlinkert/kind-offunction miniKindOf(val) {if (val === void 0) return 'undefined'if (val === null) return 'null'const type = typeof valswitch (type) {case 'boolean':case 'string':case 'number':case 'symbol':case 'function': {return type}default:break}if (Array.isArray(val)) return 'array'if (isDate(val)) return 'date'if (isError(val)) return 'error'const constructorName = ctorName(val)switch (constructorName) {case 'Symbol':case 'Promise':case 'WeakMap':case 'WeakSet':case 'Map':case 'Set':return constructorNamedefault:break}// otherreturn type.slice(8, -1).toLowerCase().replace(/\s/g, '')}function ctorName(val) {return typeof val.constructor === 'function' ? val.constructor.name : null}function isError(val) {return (val instanceof Error ||(typeof val.message === 'string' &&val.constructor &&typeof val.constructor.stackTraceLimit === 'number'))}function isDate(val) {if (val instanceof Date) return truereturn (typeof val.toDateString === 'function' &&typeof val.getDate === 'function' &&typeof val.setDate === 'function')}
测试用例
import { createStore, combineReducers, applyMiddleware } from '../src'import { logMiddleware, reduxThunk } from './middleware'const initalState = {home: {},user: {name: 'jack'},count: 0}const homeReducer = (state, action) => {return state}const userReducer = (state, action) => {switch (action.type) {case 'changeName':return {...state,name: action.data}default:return state}}const countReducer = (state, action) => {switch (action.type) {case 'increment':return state + 1case 'decrement':return state - 1default:return state}}// reducers 的 key 要和 initalState 对应const reducers = {home: homeReducer,user: userReducer,count: countReducer}// 将 reducer 组合起来const resultReducer = combineReducers(reducers)// 应用中间件const enhancer = applyMiddleware(logMiddleware, reduxThunk);const store = createStore(resultReducer, initalState, enhancer);// 订阅 store,当 store 的数据有任何变化,就会触发store.subscribe(() => console.log(store.getState()))store.dispatch({ type: 'increment' })store.dispatch({ type: 'increment' })const asyncAction = (dispatch, state) => {// 异步事件执行后,再 dispatchsetTimeout(() => {dispatch({ type: 'changeName', data: 'konsoue' })}, 2000)}store.dispatch(asyncAction);
const logMiddleware = (store) => (next) => (action) => {console.log('logMiddleware');return next(action);}const reduxThunk = (store) => (next) => (action) => {if (typeof action === 'function') {return action(store.dispatch, store.getState);}return next(action);}export {logMiddleware,reduxThunk}
