源码分析——Redux
Redux 是一个可预测且可维护的全局状态管理的 JS 库。可预测的特性主要建立在单项数据流和不可变性的基础上。
Redux 的单向数据流方式为:Store - View - Action - Store。从这种流向的处理上,循环链表有一些像。
Redux 的使用首先要创建 Store,之后 UI 界面会触发 Action 更新 Store,Store 更新后 View 随之发生变化。
全局状态管理主要解决了 React 中跨组件共享状态时候遇到的复杂性。出了降低复杂性外,还可以增加项目的可维护性,实现关注点分离。
Redux 的可预测性是基于 Immutability(不可变性)。不可变性意味着普通数据类型的 const 初始赋值后就不能在被赋值。但是在 JS 中通过 const 初始化的数组和对象并不算事严格意义上的不可变。因为 const 初始化的数组和对象,是可以改变某个索引或某个 key 的值的。
基于这样的原因,Redux 推荐在处理数组和对象的时候,要返回新的对象拷贝。也就是在实际使用时候经常见到的解构操作。
在 Redux 中的核心概念: - Store - Actions - Reducers
Store 通过 createStore 方法创建。Actions 类似一种事件,类似我们给 Dom 元素上添加的 click 类型的事件。Reducers 接收 store 和 action 对象,生成新的 store。
Actions 是一个普通 JS 对象:
const action = {
type: 'add',
payload: 1
}
Reducer 接收上面的 action,Reducer 的声明形式如:(state, action) => store
下面先创建 createStore 方法,在创建之前要先看一下如何使用 createStore,用法如下:
const initialState = {value: 0}
const reducer1 = (state = initialState, action) => {
switch(action.type == 'add') {
return {
value: state.value + action.payload
}
}
return state
}
// 创建 store 对象
const store = createStore(reducer1)
// 订阅更新
store.subscribe(() => console.log(store.getState()))
// 触发更新
store.dispatch(customAction)
从 createStore 的用法可以看出,数据的更新是通过发布订阅模式实现的。同时 store 对象上会挂在 subscribe 和 dispatch 方法。而且还提供了 getState 方法。
function createStore(reducer) {
return {
subscribe,
dispatch,
getState
}
}
我们可以先把返回的对象定义出来,然后在反推如何实现 subscribe、dispatch、getState。
首先增加 getState 方法,这个方法返回当前的 store
function createStore(reducer) {
let currentState = null
const getState = () => currentState
return {
subscribe,
dispatch,
getState
}
}
在执行 const store = createStore(reducer1) 的时候,只传入了 reducer1 作为参数,所以在 createStore 的实现里面需要能根据传入的 reducer1 计算出 currentState
为了统一执行逻辑,我们可以通过 dispatch 一个内置的 action
function createStore(reducer) {
let currentState = null
let currentReducer = reducer
const dispatch = (action) => {
currentState = currentReducer(currentState, action)
}
const getState = () => currentState
dispatch({type: 'INIT'})
return {
subscribe,
dispatch,
getState
}
}
当 createStore 执行的时候,dispatch 执行后,会调用 reducer1 返回 initialState,也就是 {value: 0},所以 currentState 就是这个值了。
接下来实现 subscribe 和 dispatch
let listenerIdCounter = 0
function createStore(reducer) {
let currentState = null
let currentReducer = reducer
let listeners = new Map()
const dispatch = (action) => {
currentState = currentReducer(currentState, action)
listeners.forEach((fn) => {
fn(currentState)
})
}
const subscribe = (fn) => {
const listenerId = listenerIdCounter++
listeners.set(listenerId, fn)
return () => {
listeners.delete(listenerId)
}
}
const getState = () => currentState
dispatch({type: 'INIT'})
return {
subscribe,
dispatch,
getState
}
}
基础版本的 Redux 已经实现了。接下来要增加 Redux 的扩展性。Redux 在 createStore 方法中设置了三个参数,最后一个参数是 enhancer 。通过传入这个参数,可以对 createStore 和 dispatch 进行拦截或包装。
先看如何使用 enhancer
// enhancer:接收 createStore,返回新的 createStore
const loggerEnhancer = (createStore) => (reducer) => {
const store = createStore(reducer)
const rawDispatch = store.dispatch
return {
...store,
// 改变了 dispatch 的行为
dispatch(action) {
console.log('dispatch:', action)
const action = rawDispatch(action)
console.log('next state:', store.getState())
return action
}
}
}
上面的 loggerEnhancer 是对 dispatch 做了拦截。在原始 dispatch 前后增加了日志功能。
通过 loggerEnhancer 的实现,很容易写出 createStore 中 enhancer 的实现逻辑
let listenerIdCounter = 0
function createStore(reducer, enhancer) {
let currentState = null
let currentReducer = reducer
let listeners = new Map()
if(enhancer) {
return enhancer(createStore)(reducer)
}
const dispatch = (action) => {
currentState = currentReducer(currentState, action)
listeners.forEach((fn) => {
fn(currentState)
})
}
const subscribe = (fn) => {
const listenerId = listenerIdCounter++
listeners.set(listenerId, fn)
return () => {
listeners.delete(listenerId)
}
}
const getState = () => currentState
dispatch({type: 'INIT'})
return {
subscribe,
dispatch,
getState
}
}
enhancer 有明显的 AOP 面向切面编程风格,但是更严谨一些的话,enhancer 是高阶函数或函数装饰的一种模式。
函数装饰相当于用高阶函数给含有函数套壳,在不该动原实现的前提下,扩展前后逻辑或改变行为。
而且,函数装饰可以像俄罗斯套娃,一层套一层的叠加。所以可以实现多 enhancer 。通过函数装饰的方式实现,可以做到不侵入函数代码,易于测试、可复用、可撤销(换回未装饰的函数)