React Hook借助useReducer, useContext代替Redux方案

当我们使用redux进行数据管理的时候,一般都是在根组件通过Provider的方式引入store,然后在每个子组件中,通过connect的方式使用高阶组件进行连接,这样造成的一个问题是,大量的高阶组件代码冗余度特别高,既然hooks带来了新特性,不如一起来用用看

目录结构图

目录示例

1
2
3
4
5
6
7
├── otherPage // 其他页面
| ├── index.jsx // 共享`Test`页面的状态;
├── Test // 测试页面
| ├── child.jsx // 测试页面的子组件。1、`useContext`定义的位置,获取父组件提供的`context`;2、`useEffect`进行异步请求;
| ├── index.jsx // 测试页面父组件。1、通过使用`Provider`提供给子组件`context`;2、`useReducer`定义的位置,引入一个`reducer`并且提供初始状态`initialState`;
| ├── otherPage.jsx // 其他页面,已删除~~
| └── reducer.jsx // 处理不同类型的`action`操作

效果图

效果图

实现

Test/reducer.jsx

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
import axios from 'axios';

function reducer (state, action) {
switch (action.type) {
case 'ADD': // 加
return Object.assign({}, state, {
type: 'add',
index: ++state.index
});
case 'DOWN': // 减
return Object.assign({}, state, {
type: 'down',
index: --state.index
});
case 'FETCH': //请求
axios('/addFetch').then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
}); ;
return Object.assign({}, state);
default: // 重置
return Object.assign({}, state, {
index: 1
});
}
};

export default reducer;

Test/child.jsx

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
import React, { useContext, useEffect } from 'react';
import { FetContext } from './index';
import { Button } from 'antd-mobile';

function DeepChild (props) {
// If we want to perform an action, we can get dispatch from context.
const dispatch = useContext(FetContext);

function handleClick () {
dispatch({ type: 'ADD' });
}

const fetch = () => {
console.log('fetch');
dispatch({ type: 'FETCH' });
};

useEffect(() => {
console.log('child useEffect', props);
});

return (
<div>
<Button onClick={handleClick} type='primary'>Child Add</Button>
<br />
<Button onClick={fetch} type='primary'>Child Request</Button>
<br />
</div>
);
}

export default DeepChild;

Test/index.jsx

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
/* eslint-disable react/prop-types */
import React, { useReducer, useEffect } from 'react';
import reducer from './reducer';
import DeepChild from './child';
import { Button, InputItem } from 'antd-mobile';
export const FetContext = React.createContext(null);

function Test (props) {
const [state, dispatch] = useReducer(reducer, {
isFetching: false,
index: props.location.state.index || 1
});

useEffect(() => {
// ...
});

const fetch = () => {
dispatch({ type: 'FETCH' });
};

const confirmClick = () => {
dispatch({ type: 'DOWN' });
};

const goOtherPage = () => {
props.history.push({
pathname: 'otherPage',
state: {
index: state.index
}
});
};

const reset = () => {
dispatch({ type: 'RESET' });
};

return (
<FetContext.Provider value={dispatch}>
<InputItem value={state.index} />
<DeepChild {...state} />
<Button onClick={confirmClick} type='warning'>Parent Reduce</Button>
<br />
<Button type='warning' onClick={fetch}>Parent Request!</Button>
<br />
<Button type='primary' onClick={reset}>Reset Index</Button>
<br />
<Button type='ghost' onClick={goOtherPage}>Next Page</Button>
</FetContext.Provider>
);
}

export default Test;

OtherPage/index.jsx

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
/* eslint-disable react/prop-types */
import React, { useReducer, useEffect } from 'react';
import reducer from '../Test/reducer';
import { InputItem, Button } from 'antd-mobile';

function OtherPage (props) {
const [state, dispatch] = useReducer(reducer, props.location.state);

useEffect(() => {
console.log('OtherPage props', state);
});

const add = () => {
dispatch({ type: 'ADD' });
};

const goBack = () => {
console.log('123');
console.log('props', props);
// props.history.go(-1);
props.history.replace({
pathname: 'Test',
state: {
index: state.index
}
});
};

return (
<div>
<InputItem value={state.index} />
<Button onClick={add} type='primary'> add</Button>
<br />
<Button onClick={goBack} type='ghost'> Go Back</Button>
</div>
);
}

export default OtherPage;

注意点

  • useEffect()可以看做是class写法的componentDidMountcomponentDidUpdate以及componentWillUnMount三个钩子函数的组合。
    • 当返回了一个函数的时候,这个函数就在compnentWillUnMount生命周期调用
    • 默认地,传给useEffect的第一个参数会在每次(包含第一次)数据更新时重新调用
    • 当给useEffect()传入了第二个参数(数组类型)的时候,effect函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。这相当于我们控制了组件的update生命周期
    • useEffect()第二个数组为空则意味着仅在componentDidMount周期执行一次