redux 核心源码解析
本文默认,你已经有了一定的es6基础。源码有删减,对其中的核心逻辑做解释
- redux 是 一个用 javascript 管理数据的容器,这个容器的状态是可以预测的。
- redux 可以跟任何是个 视图(view)层的框架结合使用
- 开发react应用,经常结合使用 redux
- redux 比较抽象,核心旨在定义一个数据读写的规范,和数据变化后的回调接口
三大原则
单一数据源 :整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的:惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改 :为了描述 action 如何改变 state tree ,你需要编写 reducers。纯函数:http://web.jobbole.com/86136/
createStore.js 核心逻辑
function createStore(reducer, preloadedState, enhancer) {
/*
参数格式/类型 匹配。
reducer:定义 state 如何更新
preloadedState: 初始状态
enhancer 是让中间件按照规定模型(洋葱模型/回形针模型)执行的函数,就是下文将会介绍的 applyMiddleware函数的返回值
*/
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)
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
/**
* 返回 store 中的 state。
* State 是只读的:这个 state 是 createStore 函数内部变量,只能通过 createStore 提供的 getState 方法读取 state。
*/
function getState() {
return currentState
}
/**
* listener 是函数,维护在内部变量 nextListeners(数组) 中
* 调用 subscribe(订阅) 方法,添加 listener 在数组中
* 并返回一个方法,可以把当前添加在数组中的 listener 函数取出。
*
* listener 的作用:当 state 发生变化的时候,listener具体去做些事情,比如更新 UI。
* 只定义做事情的时机,具体做什么由调用者自己实现
*/
function subscribe(listener) {
nextListeners.push(listener)
return function unsubscribe() {
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* dispatch的作用:更改 state
* 更改的方式:根据传入的 reducer 和 action,产生一个新的 state。
*
* action的要求:是一个纯粹的对象字面量,并且具有 type 字段
* 经过 reducer 根据 action 把 当前 state 更新。一旦更新结束,立即把数组中的每一个 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?'
)
}
currentState = currentReducer(currentState, action)
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* 可以更改 reducer
* reducer 一旦更改,立即 dispatch 一次,把 state 更新,把注册的 listener 执行一遍
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
/**
* 一旦 调用 createStore 函数,立即 dispatch 一遍,把 state 更新,把注册的 listener 执行一遍
*/
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
redux 中的中间件
redux 中的中间件一个典型的应用场景是处理异步接口返回数据
常见的 web 系统中的中间件,不侵入业务逻辑,功能优盘化,即用即插。
洋葱模型/回形针模型
middleWareA(middleWareB(middleWareC('hello world')));
/**
* 调用后的结果:
* hello world form middleWareC;form middleWareB;form middleWareA;
* */
function middleWareA(string){
return `${string} form middleWareA;`;
};
function middleWareB(string){
return `${string} form middleWareB;`;
};
function middleWareC(string){
return `${string} form middleWareC;`;
};
对上面的代码换一种展现方式
middleWareA(
middleWareB(
middleWareC('hello world')
)
);
中间件 middleWareA 要执行,先得执行 middleWareB。中间件 middleWareB 要执行,先得执行 middleWareC,数据层层传递
调用顺序为: A-->B-->C
执行结束的顺序为:C-->B-->A
这就是 洋葱模型 或者 回形针模型
下面看一个 redux-thunk 中间件
redux-thunk 核心逻辑
function createThunkMiddleware() {
/**
* dispatch 是基于 store.dispatch,且层层经过中间件包装后的方法
* getState 是 createStore 函数执行后导出的方法;
* next 为下一个中间件
* action 的预期格式为 createStore 中设计的 action的格式,是一个字面量对象。在这里做action类型的判断
*/
return ({ dispatch, getState }) => next => action => {
// 如果 action 是函数
if (typeof action === 'function') {
return action(dispatch, getState);
}
// 如果不是函数,流转到下一个中间件,期待最后的一个中间件的 action 是一个预期模式的 action
return next(action);
};
}
export default createThunkMiddleware();
applyMiddleware 核心逻辑
/**
* middlewares 是包含中间件的数组
* 需要的中间件,依次传入。如: applyMiddleware(middlewaresA, middlewaresB, middlewaresC)
* applyMiddleware 执行后的返回的函数就是一个 enhancer
*/
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// args解构后,就是 reducer, preloadedState
// 这里的 store 就是 createStore 函数生产的全局的状态树,参见上文对 createStore 的解释
const store = createStore(...args)
let dispatch = () => { };
let chain = []
/**
* 每个中间件中需要传递的数据
*/
const middlewareAPI = {
getState: store.getState,// 获取 state 的函数,用于读
dispatch: (...args) => dispatch(...args)// 更新 state 的函数,用于写。
}
// 遍历中间件
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 下面的代码的效果,就是要运用洋葱模型/回形针模型:middlewareA(middlewareB(middlewareC(store.dispatch)))
// 关于 compose 比较简单:https://github.com/reactjs/redux/blob/master/src/compose.js
// 中间件的开始是 原生的store.dispatch,结束时是 一个携带了各种中间件信息的 dispatch
dispatch = compose(...chain)(store.dispatch)
/**
* 尤其注意这里的 dispatch
* 开始时定义了一个空的 dispatch 函数,是为了给 middlewareAPI 的 dispatch 一个初始值
* 实际上真正的 dispatch 是中间件根据原生的 store.dispatch 按照洋葱模型/回形针模型,执行了一边,调用所有中间件后的 dispatch
* 这个 dispatch 携带了传入的每一个中间件的操作。一次如果此时传入 action 更新 state,所有的中间件都会被执行一遍。
*/
return {
...store,
dispatch
}
}
}