三大原则

  • 单一数据源(store)
  • state 只可读(只能通过 Reducer 生成新 State)
  • 使用纯函数执行修改(Reducer 必须为纯函数,无副作用)

单向数据流

  • dispatch 触发 action 传递数据,但应该尽量减少传递的数据
  • 如果有 middleware,在 action 发起之后,到达 reducer 之前执行
  • 到达 reducer 之后,根据 action type 返回新 state

源码解析

Redux 提供的 API 主要有:

  • createStore: 创建 store 以及提供相关操作
  • combineReducers: 合并不同的 reducer 函数
  • applyMiddleware: 添加 Redux 中间件
  • bindActionCreators: bind action 到组件中

createStore

createStore 用来创建 Redux store,用于存放所有的 state。

通过 const store = createStore(reducer, initialState) 创建 store,只能通过 dispatch action 的方式来改变 state。同时提供了 getStatesubscribe 等方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default function createStore(reducer, preloadedState, enhancer) {
// ...
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
// ...
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}

当使用 createStore 方法时,dispatch 了名为 @@redux/INIT 的 action。表示已经初始化了 store。

dispatch 方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function dispatch(action) {
// ...
try {
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
}

currentState = currentReducer(currentState, action) 执行 action 对应的 reducer 操作产生新的 state,如果有监听函数的话,按顺序触发监听函数。

监听函数由 subscribe 方法添加,subscribe 方法的返回一个 unsubscribe 函数,用来取消监听。

subscribe 方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}

实际上 subscribe 仅是在 nextListeners 中添加想要执行的监听函数,并返回了用于删除该函数的 unsubscribe 方法

combineReducers

combineReducers 用于合并不同的 reducer 函数,返回唯一的一个 reducer 函数供 createStore 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
// ...
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// ...
return function combination(state = {}, action) {
// ...
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
// ...
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}

首先处理传入的 reducer 对象(包含不同的 reducer 函数),得到键值为 function 类型的对象 finalReducers 与一个包含其键的数组 finalReducerKeys

combineReducers 返回一个 reducers 函数用于处理 action,循环所有的 reducers 传入 action 进行处理,当 state 不变时,返回原来的 state

applyMiddleware

applyMiddleware 的作用是添加中间件并执行中间件,主要是通过 compose 函数将所有的中间件串起来

1
2
3
4
5
6
7
8
9
10
11
export default 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)))
}

重点是 funcs.reduce((a, b) => (...args) => a(b(...args))) ,通过函数式编程中常见的 f(g(args)) 形式串起来,返回一个函数,函数由内到外执行传入之前传入的函数数组,函数的参数将会传递到第一个执行的函数中。

简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var f1 = function(a) {
console.log('f1: '+ a)
return a * 10
}
var f2 = function(a) {
console.log('f2: ' + a)
return a + 1
}
const funcs = [f1, f2]
const func = funcs.reduce((a, b) => (...args) => a(b(...args)))
func(1)

compose

在 applyMiddleware 中 dispatch = compose(...chain)(store.dispatch),即传入 compose 返回的函数的参数为 store 原来的 dispatch 函数。

也就是说,添加了中间件后,执行 dispatch 函数将会首先依次执行传入的中间件, 最后再执行原先的 store.dispatch

关于 Redux midddleware 有篇文章写得很不错:compose and middleware 源码分析

bindActionCreators

bindActionCreators 方法说白了就是把 action creators 转成拥有同名 keys 的对象,并使用 dispatch 将对象包裹起来,从而能够直接调用函数 dispatch action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// ...
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}

这部分得源码很简单,只是简单的判断 actionCreators,是对象的话就返回一个键值为绑定了 dispatch 的函数的对象

思考总结

很久之前就学会了 Redux 的『使用』,但是却不明白到底是什么实现的,只知道应该那样使用。

阅读了源码之后对 Redux 整个流程更加清晰,有利于之后的使用。也更加理解 Redux 单向数据流的这种思想,虽然使用上更加繁琐,但是如果数据复杂起来,这种单向数据流的管理方式还是利大于弊的。

参考链接