概述
Redux 是 JavaScript 状态容器,提供可预测化的状态管理方案。其三大原则为:
- 单一数据源 => 整个应用的 state 被储存在一颗 object tree 中,并且这个 object tree只存在于唯一一个 store 中
- state 是只读的 => 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
- 使用纯函数来执行修改 => 为了描述 action 如何改变 state tree,需要编写 reducers
纯函数: 即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用
Redux 的核心:
- store => store 是由 Redux 提供的 createStore 生成的,通过 createStore 方法创建的 store 是一个对象
- dispatch => 调度 state 的更新
- reducer => 处理 state 的更新。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。指定了应用状态的变化如何响应 actions 并发送到 store 的。
- action => action 是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源。action 是一个对象,其中包括
type
和payload
属性。描述变化 - Provider => 全局注册,在
内的组件都可拿到 state - connect => 连接组件和 store
自我实现
代码,这部分主要参照了这个系列的相关文章。相关的逻辑可以参照 commit message
官方文档
- 变化 vs 异步
- React 把处理 state 中数据的问题留给了开发者,Redux -> 处理 state 中的数据
- Redux 试图让 state 的变化变得可预测
- reducer 合成 -> 每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。开发一个函数作为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。
- combineReducers API
- store 职责:
- 维持应用的 state
- 提供
getState()
方法获取 state - 提供
dispatch(action)
方法更新 state - 通过
subscribe(listener)
注册监视器 - 通过
subscribe(listener)
返回的函数注销监听器
源码
类型
-
export type Reducer
;= (state: S | undefined, action: A) => S
createStore
类型:Function
-
参数:
- reducer
- 【可选】preloadedState
> => 初始化 state
- 【可选】enhancer
> => 增强器,用来扩展 store
的功能
- reducer
返回值:
store
, A, StateExt, Ext> & Ext>
源码分析
- 1 - 10:
preloadedState
和enhancer
不能同时为Function
- 12 - 15:如果
preloadedState
为Function
但是enhancer
为undefined
那么将preloadedState
赋给enhancer
,并且preloadedState
置为undefined
- 1 - 10:如果
enhancer
不为undefined
,那么enhancer
必须为Function
,如果是Function
直接返回enhancer(createStore)(reducer, preloadedState)
。这个在applyMiddleware
时具体讲解
// TODO: 使用一个 enhancer 作为例子
- 12 - 14: 如果
reducer
不是Function
则抛错
-
currentReducer
=> 传入的reducer
-
currentState
=> preloadedState => undefined | 用户传入的state
-
currentListeners: (() => void)[] | null
=> [] => 存储更新函数的数组 -
nextListeners
=> [] => 下次dispatch将会触发的更新函数数组 -
isDispatching
=>Lock
,当前是否在dispatch
定义 ensureCanMutateNextListeners
、getState
、subscribe
、dispatch
、replaceReducer
和 observable
函数
- 2:
dispatch
一个ActionTypes.INIT
的action
=> 使得每个reducer
返回初始state
- 4 - 11: 创建
store
变量并返回
dispatch
- 2 - 4:判断
action
是否是纯粹的对象。即不是Array
|Function
|null
,isPlainObject 定义 - 6 - 8:
action
必须有type
- 10 - 12:如果当前正在
dispatch
则不能dispatch
=> 不可以同时dispatch
两个action
- 14 - 19:执行
currentReducer
,传入currentState
和action
- 21 - 25:将
nextListeners
赋给currentListeners
和listeners
,之后循环遍历执行listener
最终return action
getState
getState
方法比较简单,就是 return currentState
subscribe
- 2 - 8:如果
listener
不是Function
或者isDispatching === true
抛错 - 10:定义
isSubscribed
=> 是否已经订阅 - 12: 执行
ensureCanMutateNextListeners
函数 - 13:将
listener
推入nextListeners
- 15:
return unsubscribe
函数 - 16 - 20:如果
isSubscribed === false
直接返回, 如果isDispatching === true
抛错 - 22:关
- 24:执行
ensureCanMutateNextListeners
函数 - 25 - 27:在
nextListeners
中删除listener
函数,并将currentListeners
置为null
ensureCanMutateNextListeners
对 currentListeners
做了浅拷贝以便于可以在 dispatch
的时候使用 nextListeners
作为临时的 listener
。这样可以防止使用者在 dispatch
的时候调用 subscribe/unsubscribe
出现 bug。避免相互影响
combineReducers
- 类型:Function
- 参数:reducers
- 返回值:Function
- 使用
combineReducers
例子
源码分析
- 2 - 13:将用户传入的
reducers
做一个shallow copy
,并且剔除不是Function
的reducer
。-
finalReducers
为有效的reducer
-
finalReducerKeys
=> 有效reducer
的key
-
- 15 - 20:执行
assertReducerShape
函数,如有Error
将Error
赋值给shapeAssertionError
- 22:返回
combination
函数
assertReducerShape
遍历给到的 reducer
- 如果在初始化
action
时返回undefined
,抛错 - 如果执行一个随机
action
时返回undefined
,抛错
combination
- 2 - 4:如果
assertReducerShape
函数执行时出错,则抛出错误 - 6 - 7: 定义
hasChanged
和nextState
变量 - 8 - 19:遍历
finalReducerKeys
数组,previousStateForKey = state[key]
,nextStateForKey = reducer(previousStateForKey, action)
,如果nextStateForKey
为undefined
抛错。将reducer
计算出来的state
存入nextState
中。并且判断previousStateForKey
和nextStateForKey
是否改变了 - 20 - 21:判断
finalReducerKeys
和传入state
的key
的length
是否改变。最终return hasChanged ? nextState : state
compose
- 类型:Function
- 参数:Function[]
- 返回值:Function
- 使用
compose
例子
源码分析
- 2 - 5:如果数组为空,返回一个空函数
- 7 - 9:如果数组的
length
为1,则返回第一个函数 - 11:对函数数组进行
reduce
操作compose(increase, square, add) // (...args) => increase(sqpare(add(...args)))
注意:Array.reduce()
的使用 => 回调函数第一次执行时,accumulator
和 currentValue
取值有两种情况:如果调用 reduce()
时提供了 initialValue
,accumulator
取值 initialValue
,currentValue
取数组中的第一个值;如果没有提供 initialValue
,那么 accumulator
取数组中第一个值,currentValue
取数组中的第二个值
Lodash compose -> flow
applyMiddleware
- 类型:Function
- 参数:Middleware[]
- 返回值:Function
- Middleware:
({getState}) => next => dispatch
=>({getState}) => dispatch => action => any
applyMiddleware的功能:改造dispatch函数,产生真假dispatch,而中间件就是运行在假真(dispatchAndLog假和next真)之间的代码。
源码分析
applyMiddleware
函数,直接返回一个函数,我们直接看这个函数即可。这个函数的结构是 (createStore) => (reducer, preloadedState?) => store
- 6:执行
createStore
函数,获取store
- 7 - 12:定义
dispatch
函数 - 14 - 17:定义
middlewareAPI
对象,这里middlewareAPI
只有两个属性,getState
和dispatch
- 18:将用户传入的
middleware[]
依次执行,并将middlewareAPI
作为参数传给各个Middleware
,Middleware
的例子如下
此时function logger({ getState }) { return next => action => { console.log('will dispatch', action) const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue } }
chain
的值为Middleware
返回值的数组// chain => [middleware1, middleware2, middleware3] next => action => { console.log('will dispatch', action) const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue }
- 19:
dispatch = compose
。使用(...chain)(store.dispatch) compose
函数,此时Middleware
中的next === store.dispatch
- 21 - 24:
return store
,这里的dispatch
是 19 行的dispatch
之后看一下 Middleware
使用场所。是作为 applyMiddleware
的参数使用。applyMiddleware
做为 createStore
的第三个参数传入 createStore
。
此时的 enhancer === applyMiddleware
,之后在 createStore
里面直接执行
applyMiddleware
,将 createStore
和 reducer
+ preloadedState
传入 applyMiddleware
,即在 applyMiddleware
代码中的第 21 行 - 第 24 行的返回值即为 createStore
的返回值
applyMiddleware
做了什么
- 执行传入的
Middleware
,并将state
作为参数传入Middleware
- 更新
dispatch
,这个更新后的dispatch
是用户传入的(强化的dispatch
),例如上述的logger
例子action => { console.log('will dispatch', action) // 此处的 next === dispatch === store.dispatch === createStore(reducer, preloadedState).dispatch const returnValue = next(action) console.log('state after dispatch', getState()) return returnValue }
注意点
-
enhancer
store 的增强器。enhancer
是一个高阶函数,返回值是一个经过包装的强化的store
。applyMiddleware
就是一个enhancer
疑问
Redux 做了什么?
- 存储 state
- 增删改查 state => 可以理解为 state 的变化
- 触发更新
middleware 的执行顺序
Redux 的中间件模型类似于 koa。在 next 前面以及 next,由外向内依次执行。当最里面的 next 执行完成之后,next 后面的代码会由内向外执行。非常类似于 Koa 的洋葱中间件模型。
dispatch
之后,Redux 是如何去处理的?
- 执行
reducer
更新currentState
- 依次执行
listener
函数
state 中的数据被修改之后,订阅者们如何去收到更新后的数据?
在 subscribe
中通过 store.getState()
获取数据
applyMiddleware
中 dispatch
为何赋值两次
第一次赋值表示 => 在 Middleware 创建时,不能进行 dispatch
第二次赋值表示 => 此 dispatch
是一个强化后的 dispatch
middlewareAPI
中的 dispatch
什么时候会被调用
注意使用场景
function logger({ getState, dispatch }) {
// 此时的 dispatch 为抛错函数,即第一次赋值的函数,即第 7 - 12 行
return next => {
// 此处的 next === store.dispatch
// 此时的 dispatch === 下面 return 的函数
return action => {
console.log('will dispatch', action)
const returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}
}
middlewareAPI
中的 dispatch
为啥要用匿名函数包裹
let dispatch = () => console.log('Error!');
const obj = {
name: 'dispatch',
dispatch,
}
obj.dispatch();
dispatch = () => console.log('Ready!');
obj.dispatch();
上述的 log 是什么结果呢?
let dispatch = () => console.log('Error!');
const obj = {
name: 'dispatch',
dispatch: (...args) => dispatch(...args),
}
obj.dispatch();
dispatch = () => console.log('Ready!');
obj.dispatch();
上述的 log 是什么结果呢?
store.subscribe
为啥要有 isDispatching
dispatch
执行时候会循环执行更新函数,要保证 listeners
数组在这时候不能被改变
以下代码作用
答:有了这一层判断,我们就可以这样传:createStore(reducer, initialState, enhancer)
或者这样:createStore(reducer, enhancer)
,其中 enhancer
还会是 enhancer
。
Redux 优势
- 纯函数,做测试的时候 easy