第一步,我需要知道怎么用,第二部才是阅读源码。
读源码前,必须先了解这个库的基本背景知识:
- 它解决了什么问题
- 基本的使用方法
redux
作为相对官方、基础的全局状态管理,多少还是要学习一下的。
还有 dva,umi-useModel
redux 有个基本原则,或者所有的数据都一个这个原则,各自管理各自的数据,只有在实际使用的时候才对数据做操作,且不能修改到源数据。
基本概念
action 动作
1
2
3
4{
type: "INCREMENT";
// 可以传入一些其他参数
}dispatch 触发
1
store.dispatch({ type: "INCREMENT" });
reducer 实际写入 store 的方法(纯函数)
1
2
3
4
5
6
7
8
9
10
11
12const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
case "ZERO":
return 0;
default:
return state;
}
};store 存放全局状态的地方
1
const store = createStore(counterReducer);
安装 redux
最开始,我们还是要先安装依赖。
1
yarn add redux
使用 react-redux
有多种方法可以与组件共享 redux-store。 我们将研究使用最新的,也是最简单的方法,即 react-redux 的 hooks-api 。
1
npm install react-redux
这个库包含了许多 hooks 例如:
useDispatch
、useSelector
、useStore
(哦吼,我没有看到其他的 hooks 了),以及Provider
。useSelector
这个 hooks 接收一个函数,该函数的第一个参数是整个 store,我们可以通过这个返回我们需要的内容。
1
2
3
4
5
6
7
8
9
10import { useSelector } from "react-redux";
const App = () => {
const store = useSelector((state) => state); // 直接返回全部全局状态
// 假设我们的全局状态中有 users 内容
const users = useSelector((state) => state.users); // 返回 users
const mans = useSelector((state) =>
state.users.filter((user) => user.gender === "man"); // 返回 gender 为 man 的人
);
return <></>;
};Provider
通过翻阅源码,找到 react-redux 关于 Provider 的一段内容
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
60function Provider(_ref) {
var store = _ref.store,
context = _ref.context,
children = _ref.children;
var contextValue = useMemo(
function () {
var subscription = createSubscription(store);
subscription.onStateChange = subscription.notifyNestedSubs;
return {
store: store,
subscription: subscription,
};
},
[store]
);
var previousState = useMemo(
function () {
return store.getState();
},
[store]
);
useIsomorphicLayoutEffect(
function () {
var subscription = contextValue.subscription;
subscription.trySubscribe();
if (previousState !== store.getState()) {
subscription.notifyNestedSubs();
}
return function () {
subscription.tryUnsubscribe();
subscription.onStateChange = null;
};
},
[contextValue, previousState]
);
var Context = context || ReactReduxContext; // 这里的 ReactReduxContext 实际上就是 const MyContext = React.createContext(defaultValue);
return /_#**PURE**_/Racet.createElement(
Context.Provider,
{
value: contextValue,
},
children
);
}
if (process.env.NODE_ENV !== "production") {
Provider.propTypes = {
store: PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired,
}),
context: PropTypes.object,
children: PropTypes.any,
};
}
export default Provider;也就是说,其实 react-redux 并不神秘,它只是一个上下文,包裹了全局的上下文。
找到一段 react 官方关于
上下文
的解释:每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。useDispatch
Hook factory, which creates a
useDispatch
hook bound to a given context.
combineReducers
combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。
合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。
1
2
3
4
5
6
7
8
9
10rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}applyMiddleware
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
redux-thunk
redux 官网关于异步逻辑的详细解释 http://cn.redux.js.org/tutorials/essentials/part-5-async-logic
其他的一些东西
关于手写 redux?不是的,相当于是原理之类的吧,转自知乎李元秋的回答。
大家都在安利自己的状态管理器,我们是否思考过一个问题,为什么需要状态管理器?我们需要先清理一下头脑:并不是因为 Redux 的存在,所以我们需要状态管理,因果关系不能搞混并不是因为写 React 应用,所以才需要状态管理,即使 VanillaJS,应用写大了,也是需要的我来做一个逐步演进的讲解,推演出为何我们需要状态管理:需求:A 模块内部状态发生了变化,需要 B 模块感知到并做出响应完成这个需求,最 plain 的写法是这样:
1 | class A { |
这里存在一个问题,A 对 B 是强依赖的,A 需要关注 B 的 interface,这是一个致命问题,随着业务复杂度的提升,整个系统会成为一团乱麻。大多数刚入行的前端程序员,写应用写到后面无法维护了,多半是这个原因。因此我们可以用一个 event bus 来解藕:// event bus(功能不完整)
1 | export const bus = (() => { |
这样一来,A 和 B 去除了直接的依赖。这里我们可以看到,虽然原始的 count 在 A 中,但实际它已经是一个脱离于 A 的状态了,我们可以让 A 自身也通过订阅方式来获得最新的 count:
1 | class A { |
这时我们关注到一个问题,我们其实是想在每一个模块中,拿到最新的 count,然而我们事实上却在每一个模块中写入了递增 count 的逻辑代码 n => this.count += n。这是为什么?因为我们的 count 仍然是每个模块自己维护一个副本,所有对它进行的操作仍需在每个模块内部完成。而很明显的,INCREASE_COUNT 的意图是明确的,并不需要在各模块中都去实现,所以我们需要把这个逻辑提取出来,以减少重复。但一旦要把对 count 的操作逻辑提取出来,我们就需要在 event bus 中维护一份 count 的状态,所以需要对 event bus 进行一次改进,改进点如下:在 bus 中维护 count 的状态在 bus 中维护对状态进行操作的逻辑每次状态变更,都执行所有 listeners(因为不确定哪些 event 会修改状态)增加接口读取最新状态// event bus(功能不完整)
1 | const initialState = { |
故事讲到这里时,我们惊讶地发现,这已经不是一个 event bus 了,分明就是一个低配版 redux。再看看基于这个低配版 redux,我们如何改造 A 和 B:
1 | import { bus } from "./event-bus"; |
— 分割线 —上面低配版的 redux,已经可以解决大部分状态管理的需求了。随着业务规模和业务特性的差异,我们对状态管理器还会有一些周边特性的需求,例如管理异步流、状态回溯、状态持久等,这些东西都是可以在这个低配版的 redux 上进行扩展改造的。总结这篇文章用一个极其简单的案例,逐步演进地推导出为何我们在写应用时,需要状态管理器,以及推导出了一个低配版 redux。只有搞清楚了状态管理器的来龙去脉,才能在市面上众多的方案中,寻找到最适合自己的一个,抑或是你根本不需要别人的方案,自己就可以来一套,代码量并不大,还可以完全针对你的业务特性进行定制。
dva
先放个屁股在这里
优先看这个一文彻底搞懂 DvaJS 原理,里面有一句话我很喜欢:
对于绝大多数不是特别复杂的场景来说,目前可以被 Hooks 取代
umi-useModel
我愿意把 useModel 成为全局状态管理的最终解决方案。