redux源码
Redux实现思想:
针对React通过setState更新组件的实现,Redux在React组件中的交互意义也就是通过action更新state。与常规事件系统不同的是,常规事件系统通过事件名挂载和触发该事件名下的绑定函数;Redux得益于React的实现机理,绑定函数最主要的工作内容重新渲染页面这一项,可以借由React自身去判断该重新渲染页面的那一部分,所以Redux最主要的目的是通过action向state传递数据,而不是判断该触发哪一个绑定函数。在Redux的实际代码实现中,通过dispatch方法派发的事件也将触发通过subscribe监听的所有listener绑定函数执行。
为着实现state更新的目的,以及面向用户的动态配置,Redux在使用createStore构建事件系统的时候,需要由用户传入reducer函数。该函数接收prevState、action两个参数,其意义是通过旧有的state、以及dispatch函数传入的action,构建新的state,最终对React页面视图产生影响。
出于模块分离、便于维护的目的,Redux提供bindActionCreators函数构建便捷的事件触发机制。在其返回函数或方法内部,起先借由bindActionCreators的传参函数生成action,然后再调用dispatch直接触发事件。这样处理,简化了dispatch函数派发事件时需要预知action的过程,职责也更为单一。其意义类同在传统的事件系统中,使用on方法绑定函数后,直接返回解绑函数,不提供remove方法。
为着处理state数据极为复杂、且操作颗粒度更为细腻的场景,Redux提供combineReducers方法,用于将多个reducer复合形成一个reducer,每个reducer用于更新state下的同名属性。
1.index.js
内核为事件系统,可用于react之外的前端框架。
import createStore from './createStore' import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' import compose from './compose' import warning from './utils/warning' function isCrushed() {} if ( typeof process !== 'undefined' && process.env.NODE_ENV !== 'production' && typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed' ) { warning( 'You are currently using minified code outside of NODE_ENV === \'production\'. ' + 'This means that you are running a slower development build of Redux. ' + 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' + 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' + 'to ensure you have the correct code for your production build.' ) } export { // 生成store实例,提供dispatch派发事件、subscribe监听事件、getState方法 createStore, // 以键值对形式合并多个reducer,同等作用于相同键值对的state combineReducers, // 生成action的同时,执行dispatch触发事件,调用listener回调 bindActionCreators, // 挂载中间件,用于装饰dispatch方法,完成打印action和state等功能 applyMiddleware, // applyMiddleware方法内部使用,外部不详 compose }
2.createStore.js
生成store实例,提供dispatch派发事件、subscribe监听事件、getState方法、replaceReducer更换reducer方法、observable将当前state作为参数传入绑定函数listener方法。
import isPlainObject from 'lodash/isPlainObject' import $$observable from 'symbol-observable' export var ActionTypes = { INIT: '@@redux/INIT' } // 参数reducer处理器,接受action作为参数,定义根据action更改state,在dispatch方法内执行 // 使用闭包存储currentState(当前state)、currentReducer(state处理机)、currentListeners(绑定函数集) export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } var currentReducer = reducer var currentState = preloadedState var currentListeners = [] var nextListeners = currentListeners var isDispatching = false function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // store.getState获取当前最新的state,无论派发dispatch方法执行了多少遍 function getState() { return currentState } // 订阅,雷同jquery事件系统的on方法,返回remove解绑函数又类同react中调用putListener后返回remove // 与常规事件系统不同的是,redux没有事件名,绑定的listener函数不根据事件名触发,而是dispatch方法执行时触发 // listener内部可以调用getState方法获取redux当前的state,设置业务逻辑 // 这样处理使得redux方案和常规事件系统的静态传参迥异,getState方法获取到的永远是最新的state function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } var isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } } // 通过currentReducer获得最新的state后,触发subscribe挂载的所有listener函数执行 function dispatch(action) { if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { var listener = listeners[i] listener() } return action } // 替换当前的ruducer,触发ActionTypes.INIT获取初始的state后,执行绑定函数listener function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) } // subscribe方法中,绑定函数linsenter需要手动调用store.getState()获取当前的state // 通过observable().subscribe(observer)挂载的函数listener,直接传入当前的state作为参数 // 返回值为带有解绑函数unsubscribe的对象 function observable() { var outerSubscribe = subscribe return { subscribe(observer) { if (typeof observer !== 'object') { throw new TypeError('Expected the observer to be an object.') } function observeState() { if (observer.next) { observer.next(getState()) } } observeState() var unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } } // 触发ActionTypes.INIT获取初始的state,执行按action为INIT绑定的函数listener dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
3.combineReducers.js
以键值对形式合并多个reducer,同等作用于相同键值对的state。
import { ActionTypes } from './createStore' import isPlainObject from 'lodash/isPlainObject' import warning from './utils/warning' var NODE_ENV = typeof process !== 'undefined' ? process.env.NODE_ENV : 'development' // 复合reducers中每个子reducer返回state为undefined时的错误文案 function getUndefinedStateErrorMessage(key, action) { var actionType = action && action.type var actionName = actionType && `"${actionType.toString()}"` || 'an action' return ( `Given action ${actionName}, reducer "${key}" returned undefined. ` + `To ignore an action, you must explicitly return the previous state.` ) } // 开发环境,复合reducers中每个子reducer不存在,或者传入每个子reducer的state不是普通对象 // 或者传入每个子reducer的state对象的属性没有对应子reducer时,警告提示 function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) { var reducerKeys = Object.keys(reducers) var argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' if (reducerKeys.length === 0) { return ( 'Store does not have a valid reducer. Make sure the argument passed ' + 'to combineReducers is an object whose values are reducers.' ) } if (!isPlainObject(inputState)) { return ( `The ${argumentName} has unexpected type of "` + ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` + `keys: "${reducerKeys.join('", "')}"` ) } var unexpectedKeys = Object.keys(inputState).filter(key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key] ) unexpectedKeys.forEach(key => { unexpectedKeyCache[key] = true }) if (unexpectedKeys.length > 0) { return ( `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` + `Expected to find one of the known reducer keys instead: ` + `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.` ) } } // action.type为初始化ActionTypes.INIT或带*,通过复合reducer的首参undefined返回initialState,不是则报错 function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { var reducer = reducers[key] var initialState = reducer(undefined, { type: ActionTypes.INIT }) if (typeof initialState === 'undefined') { throw new Error( `Reducer "${key}" returned undefined during initialization. ` + `If the state passed to the reducer is undefined, you must ` + `explicitly return the initial state. The initial state may ` + `not be undefined.` ) } var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.') if (typeof reducer(undefined, { type }) === 'undefined') { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` + `namespace. They are considered private. Instead, you must return the ` + `current state for any unknown actions, unless it is undefined, ` + `in which case you must return the initial state, regardless of the ` + `action type. The initial state may not be undefined.` ) } }) } // 复合子reducer,子reducer的键值和传入reducer的参数state的属性一一对应,修改state的该属性后返回新的state export default function combineReducers(reducers) { var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) if (NODE_ENV !== 'production') { var unexpectedKeyCache = {} } var sanityError try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } if (NODE_ENV !== 'production') { var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === 'undefined') { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
4.applyMiddleware.js、compose.js
挂载中间件,用于装饰dispatch方法,完成打印action和state等功能。
// applyMiddleware import compose from './compose' // 使用中间件装饰dispach方法 export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // middleware中间件接受参数为包含getState、dispatch的对象,返回函数,该函数接受上一个中间件返回值作为参数 chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } // compose.js export default function (...funcs) { if (funcs.length === 0) { return arg => arg } // 只有一个中间件时,中间件先接受getState、dispatch作为参数,返回函数,该函数接受dispatch作为函数并装饰,随后又返回函数,接受action作为参数 if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) // 数组的reduceRight方法,自右向左遍历执行首参函数(该函数首参为前一次函数执行的结果),次参为初始化传入首参函数的composed // reduce为自左向右遍历数组,调用首参函数逐次修改最终结果值并返回,供下一个首参函数使用 // redux中使用为middleware中间件数组作为参数funcs,dispatch作为参数args,最末一个中间件装饰dispacth方法后,交给上一个中间件装饰 // 具体功能实现为打印接收到的action和state变化,最终返回值接受action作为参数,完成派发事件的目的 return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
5.bindActionCreators.js
使用actionCreator函数生成普通对象action,并使用dispatch派发事件,currentReducer接收到action后,更改当前的state,最终触发绑定函数listener的执行。意义是传入参数,触发事件。
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { // 生成action并派发 return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators }