其实一开始看到useReducer的时候,还以为它跟react的好伙伴redux有什么关系。然而也就是借鉴了redux的设计模式的状态操作工具而已。
目前使用Redux的小伙伴们可能还需要用原来的react-redux提供的connect HOC一段时间。尽管facebook已经在做相关的hook,目前还处于不稳定的状态,使用后果自负(项目地址:https://github.com/facebookincubator/
回到useReducer,它的存在是为一步操作更新多个状态设计。
举个不太恰当的拉数据的例子。通常拉数据的时候需要显示表示正在“加载中”的动画,加载数据出错则需要提示相关的错误,成功则显示数据。这里我们假设有2个状态值isLoading和 error,加载成功的数据则是由react-redux从props里传进来:
- 加载中:isLoading = true; error = undefined; data = undefined | { … };
- 成功:isLoading = false; error = undefined; data = { … };
- 失败:isLoading = false; error = ‘Error message.’; data = { … };
比较容易想到的做法是用useState,如下:
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 1function MyComponent({ loadDataAction, data }) {
2 const [isLoading, setIsLoading] = useState(false);
3 const [error, setError] = useState(undefined);
4 const loadData = useCallback(async () => {
5 setIsLoading(true);
6 setError(undefined);
7 try {
8 await loadDataAction();
9 } catch(err) {
10 setError(err);
11 } finally {
12 setIsLoading(false);
13 }
14 });
15 return (
16 <div>
17 <button onClick={loadData} disabled={isLoading}>Load Data</button>
18 {error ? (
19 <p>{error}</p>
20 ) :
21 data ? (
22 <h3>Loaded data below</h3>
23 <div>{data}</div>
24 ) : null
25 </div>
26 );
27}
28
29
改成useReducer的话每种操作对状态造成什么影响会更加清晰,并且易于重用:
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 1function loadStateReducer(state, action) {
2 switch (action.type) {
3 case ‘loading’:
4 return {
5 isLoading: true,
6 error: undefined
7 };
8 case ‘success’;
9 return {
10 isLoading: false,
11 error: undefined
12 };
13 case ‘error’:
14 return {
15 isLoading: false,
16 error: action.error
17 };
18 }
19 return state;
20}
21
22function MyComponent({ loadDataAction, data }) {
23 const [state, dispatch] = useReducer(loadStateReducer, {
24 isLoading: false,
25 error: undefined
26 });
27 const { isLoading, error } = state;
28 const loadData = useCallback(async () => {
29 dispatch({ type: ‘loading’ });
30 try {
31 await loadDataAction();
32 dispatch({ type: ‘success’ });
33 } catch(err) {
34 dispatch({ type: ‘error’, error: err });
35 }
36 });
37 return (
38 <div>
39 <button onClick={loadData} disabled={isLoading}>Load Data</button>
40 {error ? (
41 <p>{error}</p>
42 ) :
43 data ? (
44 <h3>Loaded data below</h3>
45 <div>{data}</div>
46 ) : null
47 </div>
48 );
49}
50
51
讲真,用useState设置一个object state是等同的操作。所以用哪个看个人喜好吧。
这里要注意的是,当两个state值有相关性(比如B是根据A计算得到的结果),那就有必要考虑用object类型的state或者useReducer,否则容易遇到上下文不一致导致出现非自己期望的结果(遇到过一次,容我想到恰当的例子再谈这个问题)。
另外,我说这个例子不恰当是因为这个例子中没有在使用异步操作后考虑component实例是否还是挂载的状态,可能导致内存泄漏,并且也会有相关的报错。