redux 源码分析 (一)
首先我们看一下 redux 的代码结构:
├── applyMiddleware.js // API: applyMiddleware 中间件,对store.dispatch方法进行了改造。作用是将所有中间件组成一个数组,依次执行
├── bindActionCreators.js // API: bindActionCreators 把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它
├── combineReducers.js // API: combineReducers 把一个由多个不同 reducer 函数作为 value 的 object,合并成一个 reducer 函数
├── compose.js // API: compose 从右到左来组合多个函数。
├── createStore.js // API: createStore 创建一个 Redux store 来以存放应用中所有的 state。
├── index.js // 入口文件,负责集中暴露 API,例如 createStore 、combineReducers 等
└── utils // 工具包
├── actionTypes.js // Redux 保留的私有 action 类型。
├── isPlainObject.js // 判断是否为普通对象
└── warning.js // 在控制台中打印警告
index.js 文件
这个入口文件只有两个功能:
- 暴露所有的 API
- 对代码是否压缩进行判断
第一个功能对应的代码:
1 | // index.js |
就可以看到,最后我们导出了一个对象,这也是我们为什么可以用如下语法来引入对应方法的原因:1
import { createStore, combineReducers } from 'redux
本质就是从导出的这个对象上解构出想要的方法
可能有的小伙伴对 export、export default 等这些语法不是很了解,后面我会整理一下相关的教程
第二个功能对应的代码:
1 | /* |
我尝试着来分析一下这段代码的意义:
我们都知道,在项目上线前(生产环境),我们为了安全,都会进行代码压缩、加密、混淆,比如下面这段代码压缩前后可能会是这个样子:1
2
3
4
5
6
7
8
9function echo(stringA, stringB) {
const hello = "你好"
console.log(hello)
}
// 压缩后
function a(b, c) {
const d = "你好"
console.log(d)
}
但是,如果是开发、测试环境,其实是没有必要的。这段代码就是用来检测在非生产环境下代码是否被压缩,主要目的是担心你在开发环境用了 redux 的压缩版本,导致在你压缩项目时 redux 再次被压缩出现报错
function 有 name 属性,还有 arguments 、length 等属性 ,参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/name
这里就是通过 函数名是否还是原名来判断
createStore.js 文件
我们先大体看一下这个这个文件的输入和输出:1
2
3
4
5
6
7
8
9export default function createStore(reducer, preloadedState, enhancer) {
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
整个 createStore.js 文件最后就暴露出来 createStore 这一个函数
createStore 函数接受三个参数:reducer,preloadedState,enhancer。分别对应全局 reducer,store 中初始的 state 和 applyMiddleware 中间件函数。三个参数中,只有第一个参数是必传的,后面两个参数可选。函数的返回值是一个对象,一共暴露出来了五个方法,但是我们常用的一般只有前三个
我们使用 createStore 创建 Store 时,一般有三种用法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 第一种用法
// 只有一个reducer
const store = createStore(
reducer,
)
// 第二种用法
// 传入一个 initState
const store = createStore(
reducer,
initState,
)
// 传入一个 enhancer
const store = createStore(
reducer,
applyMiddleware(...middleware),
)
// 第三种用法
const store = createStore(
reducer,
initState,
applyMiddleware(...middleware),
)
Store 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe 监听 state 的变化,然后更新 UI。
Store 有以下职责,分别对应 Store 身上暴露出来的几个函数:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
大致的功能说完了,接下来让我们看一下源码是怎么实现的:
1 | // 调整参数,验证参数类型 |
我们后面分析道 enhancer 源码的时候,会对这部分再详细的讲解
接着往下看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// 将传入的reducer缓存到currentReducer变量中
let currentReducer = reducer
// 将传入的preloadedState缓存到currentState变量中
let currentState = preloadedState
// 定义当前的监听者队列
let currentListeners = []
// 定义下一个循环的监听者队列
let nextListeners = currentListeners
// 定义一个判断是否在dispatch的标志位
let isDispatching = false
// 判断是否能执行下一次监听队列
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 这里是将当前监听队列通过拷贝的形式赋值给下次监听队列,这样做是为了防止在当前队列执行的时候会影响到自身,所以拷贝了一份副本
nextListeners = currentListeners.slice()
}
}
...
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
// 当 store 被创建的时候, 会派发一个 "INIT" 的 action,
// 所以每个 reducer 都被执行,然后 return 出他们的初始值,
// 这些初始值组成了初始的 state tree
// dispatch一个初始化的action
dispatch({ type: ActionTypes.INIT })
这里可以看到,在我们创建 store 的时候,就已经执行了第一次的dispatch ,
派发了一个 “INIT” action 。然后回执行 reducer(如果是 combineReducers 合成的,也会执行)。这一步的作用是用来初始化 state 树。这也是为什么我们即使没有传入 initState 也可以拥有初始化 state 的原因
接下来我们理所应当的进入到 dispatch 函数来一探究竟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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75/**
* Dispatches an action. It is the only way to trigger a state change.
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* 派发一个 action ,这是触发 state 改变的唯一方式
* 被用来创建一个 store 的 reducer 函数,会被一个 action 来触发调用
* 这个 reducer 的返回值就是下一次 state 的值,并且 listeners 也会被通知调用
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* 这个最基本的实现只支持扁平化的 actions,如果你想派发一个 promise、Observable、thunk
* 或者其他东西,你需要用中间件包装你的 createStore 函数
* 即使被中间件包装,最后还是派发了一个扁平的 action
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
// redux 中通过 dispatch 一个 action ,来触发对 store 中的 state 的修改
// 参数就是一个 action
function dispatch(action) {
// 这里判断一下 action 是否是一个扁平的对象,如果不是则抛出错误
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// action中必须要有type属性,否则抛出错误
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
// 如果上一次dispatch还没结束,则不能继续dispatch下一次
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
// 将isDispatching设置为true,表示当次dispatch开始
isDispatching = true
// 利用传入的reducer函数处理state和action,返回新的state
// 推荐不直接修改原有的currentState
currentState = currentReducer(currentState, action)
} finally {
// 当次的dispatch结束
isDispatching = false
}
// 每次dispatch结束之后,就执行监听队列中的监听函数
// 将nextListeners赋值给currentListeners,保证下一次执行ensureCanMutateNextListeners方法的时候会重新拷贝一个新的副本
// 简单粗暴的使用for循环执行
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
// 最后返回action
return action
}
然后是 getState 函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
// 获取当前的state
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
subscribe 函数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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* 添加一个事件监听。每次派发一个 action 或者 state 树发生变化都会依次执行
* listener 里面的所有监听函数
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
// 往监听队列里面去添加监听者
function subscribe(listener) {
// 监听者必须是一个函数
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
// 如果正在 isDispatching
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
)
}
// 声明一个变量来标记是否已经subscribed,通过闭包的形式被缓存
let isSubscribed = true
// 创建一个当前currentListeners的副本,赋值给nextListeners
ensureCanMutateNextListeners()
// 将监听者函数push到nextListeners中
nextListeners.push(listener)
// 返回一个取消监听的函数
// 原理很简单就是从将当前函数从数组中删除,使用的是数组的splice方法
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
1 | /** |
combineReducers.js
首先看一下整体1
2
3
4...
export default function combineReducers(reducers) {
...
}
和之前一样,这个文件只导出了单独的 combineReducers 函数
combineReducers 只接受一个对象作为参数, 我们通常这么使用1
2
3
4
5
6
7
8
9
10
11const todoApp = combineReducers({
visibilityFilter,
todos
})
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
这两种写法是一样的
1 |
|
bindActionCreators.js
首先先认识 actionCreators, 简单来说就是创建 action 的方法,redux 的 action 是一个对象,而我们经常使用一些函数来创建这些对象,则这些函数就是 actionCreators
而这个文件实现的功能,是根据绑定的 actionCreator,来实现自动dispatch的功能
actionCreators 接受两个参数,第一个参数是个对象,第二个参数是 dispatch1
2
3
4
5
6
7
8
9
10
11
12
13
14// actionCreators
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
};
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
};
}
第一个参数的格式1
2
3
4
5
6
7
8
9
10
11
12
13
14{
addTodo: function addTodo(text) {
return {
type: 'ADD_TODO',
text
};
},
removeTodo: function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
};
}
}
用法:
boundActionCreators..addTodo(‘Use Redux’)
1 | function bindActionCreator(actionCreator, dispatch) { |
compose
1 | compose( |
1 | /** |