Redux到底做了什么?

objective 为什么写这篇文章?

我希望了解一个状态管理工具,而 Redux 是一个比较知名的且比较简洁的状态管理容器。不考虑作者水平存在的争议性,Redux 仍是具有很多值得学习的东西。并且 Redux 的源码很短,可以快速水一篇文章(大雾)。

这里简单整理一下这个状态管理容器的实现思路。

section1 使用

文档有点多,直接看官网给的例子好了,这里选了一个比较简单的计数器demo 地址,这里使用这个例子讲解 redux 的使用。

  1. 定义一个 reducer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export default (state = 0, action) => {
    switch (action.type) {
    case "INCREMENT":
    return state + 1;
    case "DECREMENT":
    return state - 1;
    default:
    return state;
    }
    };
  2. 使用这个 reducer 来创建一个 store

    1
    2
    import counter from "./reducers";
    const store = createStore(counter);
  3. 通过这个 store 来 dispatch 相应的 action

    1
    store.dispatch({ type: "INCREMENT" });

    上面的使用方法很简单,不考虑 React 中结合使用和比如异步等情况,Redux 的使用非常简洁,并且会给了你一种你上你也行的感觉。

事实上,真的是你上你也行。

section2 我的看法

因为我对 React 使用的不算很多,并且少 React 的我也很少接触 Redux 生态。所以,这里说说我对 Redux 的认知,如果不对可以联系我指正。

从官网第一句:Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

这个可预测性才是 Redux 的核心。

Redux 通过以下三点实现了数据的可预测化:

  • 单一数据源(Single Source of Truth)
  • 数据 readonly,仅可以通过 action 改变
  • action 处理后必须返回一个全新的 state(reducer 必须 pure)

reducer 保持 pure 的优势:

  1. 方便测试
  2. 减少 state 比较开销

下面开始看代码:

section3 源码

代码位置哈:createStore

一下代码会去掉一些类型标注,可以自己点上面的链接看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createStore(reducer, preloadedState, enhancer) {
// 扩展相关与错误处理。。。
let currentReducer = reducer;
let currentState = preloadedState as S;
let currentListeners: (() => void)[] | null = [];
let nextListeners = currentListeners;
let isDispatching = false;

/* ... 先省略这部分直接看这个方法返回了什么*/

const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
} as unknown;

return store;
}

这里挺容易理解,返回一个带有一些方法的 store,store 中持有单一数据源(state)

一个个看吧:

dispatch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function dispatch(action: A) {
/*handle error*/
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.");
}
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;
}

这里简单说说 isDispatching,我猜测这个变量应该是用来防止异步的 Reducer 造成数据的混乱。

然后就是正常的指定 reducer 获取新的 state,执行所有的 listener 回调。

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
function subscribe(listener: () => void) {
if (isDispatching) {
/*这里防止非reducer前的listener执行错误*/
throw new Error();
}

let isSubscribed = true;

ensureCanMutateNextListeners();
nextListeners.push(listener);

return function unsubscribe() {
if (!isSubscribed) {
return;
}

if (isDispatching) {
throw new Error();
}

isSubscribed = false;

ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}

看一下 ensureCanMutateNextListeners:

1
2
3
4
5
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}

这个方法将用于确保 nextListener 与 currentListeners 两个引用指向不会指向同一空间。

也就是说 nextListeners 相当于一个缓冲区,如果在 dispatch 前 listener 的监听发生变化,则改变 nextListeners 即可,然后在 dispatch 完成调用回调的时候将 currentListeners 指向 nextListeners 空间。然后继续收集 listener 则会在新的 nextListeners 收集。

再看 replaceReducer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
if (typeof nextReducer !== "function") {
throw new Error("Expected the nextReducer to be a function.");
}

// TODO: do this more elegantly
((currentReducer as unknown) as Reducer<NewState, NewActions>) = nextReducer;

dispatch({ type: ActionTypes.REPLACE } as A);
// change the type of the store by casting it to the new store
return (store as unknown) as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext;
}

可以看到这里用新的 reducer 直接替换的老的 reducer。

查看评论