Open Lemonreds opened 6 years ago
Redux的核心功能基本都在 createStrore 中,我们使用Redux也由此方法开始,可以说是最为重要的一个方法,下面我们就来了解一下createStore究竟是怎么工作的。
首先来看 createStore 的方法签名:
function createStore(reducer, preloadedState, enhancer)
方法接收三个参数:
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
返回一个Store对象,对象暴露以上的方法给外部:
创建并返回一个维护State树的Store对象,更新State树的唯一方法是调用 Store.dispatch(),一个Redux应用应该只有一个Store,如果需要模块化state树的处理逻辑,则可以编写多个reducer,使用Redux另一个API (combineReducers,后续会说到)合并成一个 reducer 作为参数传入 createStore。
接下来看看详细代码:
方法的开头首先对传入的参数进行了类型判断:
/** * 这里使得 createStore 可以忽略参数 preloadedState * 从而可以这样调用: createStore(reducer,enhancer) */ if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } /** * 检查 enhancer 是否是函数 * 主要是应用于 Redux 中间件 */ if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } /** * 暂且跳过这里 applyMiddleware 时再进行讲解 * 可以暂时这样认为: * enhancer(createStore)(reducer, preloadedState) 这一系列函数执行完后 * 返回的也是一个Store对象 只不过这个对象被加工了 */ return enhancer(createStore)(reducer, preloadedState) } /** * 检查 reducer 是否是函数 */ if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') }
接着初始化一些变量:
// 当前的 reducer 函数 // 主要用于 Store.replaceReducer() 替换 reducer 时使用 let currentReducer = reducer // currentState 就是 store 中的状态树,这里先对它进行了初始化 // Store.getState() 返回此变量 let currentState = preloadedState // 以下的两个变量主要用于 Store.subscribe()方法 // 当前的订阅者 let currentListeners = [] // 下一次 Store.dispatch() 更新state树 需要发出通知的订阅者 let nextListeners = currentListeners // 是否正在调用 Store.dispatch() 方法 // Store.dispatch方法调用时,会禁止一些操作执行 let isDispatching = false
接下来一个个看看Store暴露的接口方法:
Store.getState() 是最简单的一个方法,直接返回当前的状态树,也就是 currentState 变量。
/** * 返回当前的状态树 state * 不允许在调用 Store.dispatch() 方法时获取状态树 */ function getState() { if (isDispatching) { throw new Error( 'xxxxxx' ) } return currentState }
Store.dispatch()方法,传入一个action对象,更新state树的唯一途径就是调用此方法。
调用例子:
Store.dispatch({ type: "ADD_TODO", text: "Read the source code." })
同样,函数的开始先对入参合法性进行判断(因为JavaScript是弱类型语言- -、):
function dispatch(action) { // 检查 action 是否为纯对象 // 纯对象即是通过 {} 或者 new Object() 创建的对象,其原型为 Object.prototype if (!isPlainObject(action)) { throw new Error( 'xxxxxx' ) } // 检查 action.type 是否存在 // type 字段是必须的 if (typeof action.type === 'undefined') { throw new Error( 'xxxxx' ) } // 同一时刻只能执行一个 dispatch 函数 // 为了防止在 dispatch 函数嵌套调用 dispatch 函数 if (isDispatching) { throw new Error('xxxx') }
入参类型都正确后,接着将当前的 state树 和传入的 action 传递给 reducer 执行 ,如何去更新state树的数据处理逻辑是由reducer决定的,reducer 执行相关的数据处理逻辑,返回新的state树,从而更新state树:
try { // 设置变量 // 正在更新状态树 一些操作将被禁止 isDispatching = true // 传入 state,action 交由 reducer 函数 执行 // reducer 返回新的 state currentState = currentReducer(currentState, action) } finally { // 状态更新完成 设置为false isDispatching = false }
由于state树的更新,需要向订阅监听的函数发出通知:
/* * 更新了state树 向所有的订阅者发出通知 * 任何时刻 增加一个监听器时(调用Store.subscribe()) 首先会将监听器加到 nextListeners 数组中 * 直到下一个 dispatch 发起执行时 才会同步 currentListeners 和 nextListeners * 然后对最新的订阅数组发出通知 执行所有订阅函数 */ // 同步最新的订阅数组 const listeners = (currentListeners = nextListeners) // 执行所有的监听函数 for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() }
函数的最后,返回了入参action:
// 返回了原有的入参 action // 在应用redux中间件时 此返回将发挥极大的作用 return action
这个返回其实对于调用者而言,并没有多大用处。它真正发挥作用的时候是在于扩展Redux中间件的时候,也就是applyMiddleware方法,后续会进一步解读。
dispatch方法完成的任务主要有三个:
接下来看Store.subscribe(),方法的作用是订阅state树的变化,每次执行完dispatch方法后,都会触发监听,执行传入的监听函数。
// 注册一个监听函数 当state树更新时 输出最新的state值 Store.subscribe(()=>{ console.log(Store.getState()) })
在对subscribe方法解读之前,先看看方法内部用到的一个函数 ensureCanMutateNextLisenteners。
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { // slice() 不传入参数的话 就是拷贝整个数组,返回一个新的数组 nextListeners = currentListeners.slice() } }
函数十分简单,判断当前订阅和下次订阅是否指向同一个数组,如果是的话就拷贝数组,使得 nextListeners 和 currentListneres 保存两份数据一样的数组。但是为什么要这样做呢?首先应该明确的是,不管是注册监听,还是注销监听,都只会在 nextListeners 上操作,直到 dispatch 被调用时,才会同步 currentListneres 和 nextListeners ,也就是 dispatch 中的 :
const listeners = (currentListeners = nextListeners)
dispatch完成后 currentListeners 和 nextListeners 这两个变量就会指向同一个数组,在此之后,如果你注册注销监听,在 nextListeners 上操作的同时,势必也会影响到 currentListeners 这个变量,这样就混淆了 currentListeners 和 nextListeners 两个变量的作用,所以需要一个 ensureCanMutateNextLisenteners 函数,保证在nextListeners 上注册注销监听,都不会影响到 currentListeners 。具体调用场景看下面对subscribe方法的解读:
同样,subscribe 方法一开始也是对入参 listener 进行判断:
// 检查参数类型是否正确 if (typeof listener !== 'function') { throw new Error('xxxx') } // 不允许 dispatch 函数正在执行的时候进行订阅 if (isDispatching) { throw new Error( 'xxxx' ) }
接着用闭包保存了一个变量,用来标记监听函数是否正在监听
let isSubscribed = true
因为要对 nextListeners 进行操作,所以调用了 ensureCanMutateNextLisenteners ,确保操作不会影响到 currentListeners
// 确认修改 nextListeners 时 不影响到 currentListeners ensureCanMutateNextListeners() // 将新的监听函数加入到下一次监听函数执行队列 nextListeners.push(listener)
函数的最后,返回一个函数,用来注销监听:
// 返回一个取消监听的函数 return function unsubscribe() { if (!isSubscribed) { // 当然 取消订阅只能取消一次... return } // 不允许在 dispatch 的时候取消监听函数 if (isDispatching) { throw new Error( 'xxxxxxxxxxxx' ) } // 注销监听 isSubscribed = false // 确认修改 nextListeners 时 不影响到 currentListeners ensureCanMutateNextListeners() // 删除这个监听函数,下一次 dispatch 时会生效 const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) }
subscribe 方法完成的任务只有两个:
接下来看看同样简单的 Store.replaceReducer() 方法:
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('xxxxx') } // 更新reducer currentReducer = nextReducer // 会触发一个Redux独有的action,来确定状态树是否能响应新的reducer变化 dispatch({ type: ActionTypes.REPLACE }) }
方法的作用就是替换reducer函数,达到热更新reducer的效果,一般很少用到。值得注意的是,方法之中 dispatch 了一个 action:
dispatch({ type: ActionTypes.REPLACE })
可见,Redux在实现内部自己也会触发一些action,具体的作用来看看 ActionTypes 这个常量的定义。
ActionTypes 来自 ./src/utils/actionTypes.js ,源码如下:
const randomString = () => Math.random() .toString(36) .substring(7) .split('') .join('.') const ActionTypes = { INIT: `@@redux/INIT${randomString()}`, // str REPLACE: `@@redux/REPLACE${randomString()}`, // str PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}` // fun } export default ActionTypes
就是通过 randomString 生成三个任意的,不可预测的 action.type 名称,这些 action 仅供 Redux内部使用。例如上面使用到了 REPLACE 这个action,整个createStore代码的最后部分,可看到还发起了一个 type为 INIT 的 action :
// 偷偷发起了一个action dispatch({ type: ActionTypes.INIT }) // 然后就是正常的导出: return { //.... }
为什么Redux需要使用到这些私有action呢? 其实非常简单,原因在于Redux对reducer计算新的state树的时候有严格的要求,为了严格约束reducer,需要触发一定的action来检测返回的新state树是否符合预期,具体要求是这样的:
也就是以下两种情况:
第一点是也为初始化state树,使得不做任何更新之前,都能通过getState()访问到state树。 第二点中,如果 dispatch 了一个未知的action,reducer什么也没有返回,即函数默认返回了undefined,则在 dispatch 在更新state树的时候:
currentState = currentReducer(currentState, action)
就会设置当前状态 currentState = 'undefined' ,导致丢失state的值。
createStore源码不难阅读,最重要的是要理解state,action,reducer等概念的作用。方法返回的Store对象基本包含了我们使用Redux的方法。但其中没有包含更多细节,比如我们要如何使用多个Reducer划分数据处理逻辑,如何应用中间件等,这些操作还要看Redux提供的其他方法。
Redux的核心功能基本都在 createStrore 中,我们使用Redux也由此方法开始,可以说是最为重要的一个方法,下面我们就来了解一下createStore究竟是怎么工作的。
createStore 方法预览
方法的签名
首先来看 createStore 的方法签名:
方法接收三个参数:
方法的返回
返回一个Store对象,对象暴露以上的方法给外部:
方法的作用
创建并返回一个维护State树的Store对象,更新State树的唯一方法是调用 Store.dispatch(),一个Redux应用应该只有一个Store,如果需要模块化state树的处理逻辑,则可以编写多个reducer,使用Redux另一个API (combineReducers,后续会说到)合并成一个 reducer 作为参数传入 createStore。
详细解读 CreateStore
接下来看看详细代码:
方法的开头首先对传入的参数进行了类型判断:
接着初始化一些变量:
接下来一个个看看Store暴露的接口方法:
Store.getState()
Store.getState() 是最简单的一个方法,直接返回当前的状态树,也就是 currentState 变量。
Store.dispatch()
Store.dispatch()方法,传入一个action对象,更新state树的唯一途径就是调用此方法。
调用例子:
同样,函数的开始先对入参合法性进行判断(因为JavaScript是弱类型语言- -、):
入参类型都正确后,接着将当前的 state树 和传入的 action 传递给 reducer 执行 ,如何去更新state树的数据处理逻辑是由reducer决定的,reducer 执行相关的数据处理逻辑,返回新的state树,从而更新state树:
由于state树的更新,需要向订阅监听的函数发出通知:
函数的最后,返回了入参action:
这个返回其实对于调用者而言,并没有多大用处。它真正发挥作用的时候是在于扩展Redux中间件的时候,也就是applyMiddleware方法,后续会进一步解读。
dispatch方法完成的任务主要有三个:
Store.subscribe()
接下来看Store.subscribe(),方法的作用是订阅state树的变化,每次执行完dispatch方法后,都会触发监听,执行传入的监听函数。
调用例子:
在对subscribe方法解读之前,先看看方法内部用到的一个函数 ensureCanMutateNextLisenteners。
函数十分简单,判断当前订阅和下次订阅是否指向同一个数组,如果是的话就拷贝数组,使得 nextListeners 和 currentListneres 保存两份数据一样的数组。但是为什么要这样做呢?首先应该明确的是,不管是注册监听,还是注销监听,都只会在 nextListeners 上操作,直到 dispatch 被调用时,才会同步 currentListneres 和 nextListeners ,也就是 dispatch 中的 :
dispatch完成后 currentListeners 和 nextListeners 这两个变量就会指向同一个数组,在此之后,如果你注册注销监听,在 nextListeners 上操作的同时,势必也会影响到 currentListeners 这个变量,这样就混淆了 currentListeners 和 nextListeners 两个变量的作用,所以需要一个 ensureCanMutateNextLisenteners 函数,保证在nextListeners 上注册注销监听,都不会影响到 currentListeners 。具体调用场景看下面对subscribe方法的解读:
同样,subscribe 方法一开始也是对入参 listener 进行判断:
接着用闭包保存了一个变量,用来标记监听函数是否正在监听
因为要对 nextListeners 进行操作,所以调用了 ensureCanMutateNextLisenteners ,确保操作不会影响到 currentListeners
函数的最后,返回一个函数,用来注销监听:
subscribe 方法完成的任务只有两个:
Store.replaceReducer()
接下来看看同样简单的 Store.replaceReducer() 方法:
方法的作用就是替换reducer函数,达到热更新reducer的效果,一般很少用到。值得注意的是,方法之中 dispatch 了一个 action:
可见,Redux在实现内部自己也会触发一些action,具体的作用来看看 ActionTypes 这个常量的定义。
解读 ActionTypes.js
ActionTypes 来自 ./src/utils/actionTypes.js ,源码如下:
就是通过 randomString 生成三个任意的,不可预测的 action.type 名称,这些 action 仅供 Redux内部使用。例如上面使用到了 REPLACE 这个action,整个createStore代码的最后部分,可看到还发起了一个 type为 INIT 的 action :
为什么Redux需要使用到这些私有action呢? 其实非常简单,原因在于Redux对reducer计算新的state树的时候有严格的要求,为了严格约束reducer,需要触发一定的action来检测返回的新state树是否符合预期,具体要求是这样的:
也就是以下两种情况:
第一点是也为初始化state树,使得不做任何更新之前,都能通过getState()访问到state树。 第二点中,如果 dispatch 了一个未知的action,reducer什么也没有返回,即函数默认返回了undefined,则在 dispatch 在更新state树的时候:
就会设置当前状态 currentState = 'undefined' ,导致丢失state的值。
最后
createStore源码不难阅读,最重要的是要理解state,action,reducer等概念的作用。方法返回的Store对象基本包含了我们使用Redux的方法。但其中没有包含更多细节,比如我们要如何使用多个Reducer划分数据处理逻辑,如何应用中间件等,这些操作还要看Redux提供的其他方法。