一.设计者的品味
在某个特定背景(即需求)下,任何工具的产生,都是为了解决某个问题而存在,并且不断优化的过程。
在 props多级传递state引发不可测的连锁反应 即系统复杂度提升 下,redux的产生,使得state的变化可预测,优化是随着开发场景的需要而不断产生的:函数职责单一的合并而产生combineReducers,需dispatch增强而产生applyMiddleware......
好设计是简单的设计。简单意味着符合我们的思维 --- 控制事情的发展。为了state变化的可预测,可预测通常意味着命令式(即 “要怎样”)。所以,redux采用:state只读(即 不可变),action描述变化,reducer纯函数执行修改(即 变化)后返回新state。
所以,只有触发action才能改变state,为了连接action和state而产生reducer。
封装 state、action、reducer 而 产生 store。store相当于一个黑盒子,只需输入action,黑盒内部通过调用reducer和插件middleware处理state,输出 预期结果。
纯函数:相同输入映射相同输出,输出结果只有参数决定,无副作用。
例1,我对着 安静的手机说,“siri 播放 《痒》”,手机播放了痒。
state对应手机状态,初始state为 安静,action描述变化(即 播放痒), reducer是 siri执行修改(即 播放痒),且无副作用(即 不会打开音乐以外的应用),新state为 播放痒的手机。
reducer(state, action) 连接了 state 和 action ,表现结果为 手机由 “安静” 到 “播放痒”。
例2,此时我又喊,“siri 发送查询话费的短信”,几秒钟后收到话费余额为0的短信。
state为 播放痒的手机且无话费短信,action为 发送短信,reducer执行了查询话费,新state为 播放痒的手机且有话费短信。reducer包含了两个动作:播放痒,发短信。
单一即简单。应用 函数单一职责,将 原reducer 拆分成两个reducer 并携带对应的state,分别拥有 播放痒(即reducer1) 、 发短信(即 reducer2)。应用 数据源单一, 将state以object tree形式 存储到唯一一个store。故 开发一个新reducer(即 combineReducers) 调用 reducer1 和 reducer2 ,返回新state作为object tree根节点 管理整个应用state。
单一数据源 因redux应用只有一个store,故拆分数据处理逻辑,应使用reducer组合。
每个reducer返回的新state 作为 object tree 的 节点。
相当于所有reducer组成一棵树rooReducer,store.js中调用createStore( rootReducer, initialState, applyMiddleware(...middlewares) ) 而产生 唯一一个store。
此时,redux的三大原则跃然纸上:单一数据源,state是只读的,用纯函数执行修改。
同时,闭包 在源码中展现的淋漓尽致。
二. redux核心源码
1. 分析
通过createStore()创建的黑盒子store: 如何生成黑盒子 ---> 黑盒子的规则 ---> 黑盒子内部运行原理
看什么?专注于黑盒子store的产生 即createStore()函数,关注其中使用的辅助函数。
createStore(reducer, preloadedState, enhancer)涉及三个参数:
第一个参数reducer 相当于上文说的 object tree,即需要 combineReducers 去合并出一个根节点。该参数是对象。combineReducers源码见 “三.辅助函数源码 删减版” 。
第二个参数preloadedState 是初始化的state。该参数可省略。
第三个参数enhancer 是增强store功能,通常传入 applyMiddleware()函数。applyMiddleware源码见 “三.辅助函数源码 删减版”。
createStore()可 两个参数或三个参数,故其内部需 if 判断 传入参数的个数。
createStore()创建出来的黑盒子(即 对象),提供了与state相关的4个API:(1)getState()返回当前的state树(2)dispatch()派发action,改变state的唯一途径(3)subscribe()添加监听函数,每次dispatch时调用(4)replaceReducer()替换当前用于计算的reducer
2. 源码
createStore():执行增强函数enhancer,返回 包含4个API 的对象。
function createStore(reducer, preloadedState, enhancer) {
// 传入参数为(reducer, enhancer)
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error()
}
// 使用 enhancer 对 createStore 增强
return enhancer(createStore)(reducer, preloadedState)
}
let currentReducer = reducer // store对应的reducer
let currentState = preloadedState // 初始状态
let currentListeners = [] // 监听函数listener的集合
let nextListeners = currentListeners // listeners副本:增删操作在此进行
let isDispatching = false // 是否正在dispatch 的标志位
// 返回当前state
function getState() {
if (isDispatching) {
throw new Error()
}
return currentState
}
// 订阅 store变化
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error()
}
if (isDispatching) {
throw new Error()
}
let isSubscribed = true
// listeners副本 添加监听函数
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error()
}
isSubscribed = false
// listeners副本 删除监听函数
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// 派发action,更改state
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error()
}
if (typeof action.type === 'undefined') {
throw new Error()
}
if (isDispatching) {
throw new Error()
}
try {
// 执行reducer
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 执行监听函数
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 动态修改reducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error()
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
// 保证 nextListeners 和 currentListeners 不会指向同个数组
function ensureCanMutateNextListeners() {
// 指向相同数组,nextListeners作为副本
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
3. 潜在问题
引子:有没有感觉getStore直接返回currentState这种做法不太严谨?
问题:直接返回 对象引用,对于刚上手redux的开发者极有可能对其进行篡改。
解决方案:源码中 返回一个副本。
或者在开发中使用 immutable 保证数据不可变。
三. 辅助函数源码 删减版
1. combineReducers
作用:将多个reducer函数合并成一个reducer函数。即单纯把一系列的函数以树的形式挂到一个root函数上。
执行时机是在dispatch时, 执行 传入reducers对象 中的各个reducer,记录返回的新state。
let reducers = combineReducers({ music: musicReducer, message: messageReducer });思路:遍历reducers执行各个reducer,使用 nextState 记录 执行各个reducer返回的新state,返回nextState。
function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
// 过滤 reducers ,保证 其key都有对应reducer
const finalReducers = reducers
const finalReducerKeys = reducerKeys
return function combination(state,action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
// 取reducers对象中的key, 即 music、message
const key = finalReducerKeys[i]
// 取reducers对象中对应的reducer, 即 musicReducer、messageReducer
const reducer = finalReducers[key]
// 初始state
const previousStateForKey = state[key]
// reducer执行修改,返回的新state
const nextStateForKey = reducer(previousStateForKey, action)
// 更新state
nextState[key] = nextStateForKey
// 判断state是否改变。有一个reducer返回新state,则标志位为true。
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
// state 改变则返回 新state,未变则返回 初始state
return hasChanged ? nextState : state
}
}
2. applyMiddleware
作用:执行各个中间件,对dispatch进行扩展,增强dispatch功能。最常见的dispatch接收函数,就是redux-thunk中间件。redux-thunk源码 见4.
在createStore()中若有enhancer则执行,而通常传入enhancer实参为applyMiddleware
enhancer(createStore)(reducer, preloadedState)接收中间件数组middlewares,map循环执行各个中间件,得到 改造dispatch的函数集 数组
function applyMiddleware(...middlewares) {
// 返回 增强版的createStore方法(增强体现在middlewares对dispatch的改造)
return createStore => (...args) => {
// ...arg就是reducer,[preloadedState]
const store = createStore(...args);
// 初始化dispatch,不允许构造中间件过程中 调用dispatch
// 后续会被更改,映射到闭包中
let dispatch = () => {
throw new Error()
}
const middlewareAPI = {
getState: store.getState,
// 闭包,保存了上下文的dispatch变量。此值最终为 改造后的dispatch
dispatch: (action, ...args) => dispatch(action, ...args)
}
// middleware 通常是对 dispatch() 的两层封装。即 解封两次 可得到 改造版dispatch
// 构造中间件,解封一次。
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 函数柯里化,依次使用中间件对dispatch进行改造,最终改造结果用dispatch接收,通过闭包映射
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}3. compose
作用:根据函数柯里化,使用reduce依次执行各个中间件,改造dispatch。
因为使用reduce,所以中间件从右向左执行,故开发中可能对中间件的顺序有一定要求。
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}4. redux-thunk:
作用:作为中间件, 改造dispatch,可使其处理异步请求。
被调用时,如下
// 构造中间件,解封一次。const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 函数柯里化,依次使用中间件对dispatch进行改造,最终改造结果用dispatch接收,通过闭包映射dispatch = compose(...chain)(store.dispatch)思路:如果传入的action是函数,则执行action,并传入改造后的dispatch;如果是对象,则调用原dispatch派发action。
function createThunkMiddleware(extraArgument) {
// 第一个参数:middlewareAPI,其中的dispatch为 改造版dispatch
// 第二个参数:store.dispatch 即next为 原dispatch
// 第三个参数:action
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action); // 原dispatch会返回 action,继续供下一个中间件使用
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;四.React-Redux
redux是状态管理方案,它提供了一个状态容器。
直接将状态容器应用到项目中,会造成react组件和redux的高度耦合。
故 react-redux 封装 状态容器 后,可与 项目 进行连接,降低耦合度。
react-redux提供了Provider让所有组件可使用store,提供了高阶函数connect生成新组件。
五.改造自己的中间件
开发时,参照redux-thunk对dispatch封装 两层 回调函数。
可根据项目需要,开发适合项目的中间件,比如说 dispatch监听请求的状态,封装ajax,在发送请求后的回调函数中,根据响应状态进行不同的操作。