Hooks异步请求实例-useReducer、useContext和useEffect替代Redux
Hooks从诞生至今已经有了长足的进步,不得不说hooks的出现是React的一个标志性里程碑。用hooks来解决开发中的问题大大提升了我们日常工作的效率,本文是针对异步请求数据的一个案例,目前只针对useReducer、useContext、useEffect进行探讨,同时还需要使用过Redux进行过异步action添加。
非Hooks实现方案
在React的生命周期中,我们知道componentDidMount里我们最适合去进行异步请求,因此在不需要考虑状态管理时,这种方法很简单:
1 2 3 4 5 6
| class App extends React.Component{ componentDidMount(){ axios.get('/your/api') .then(res=>/*...*/) } }
|
随后引入Redux进行状态管理
当你决定使用Redux进行状态管理时,比如将异步获取到的数据储存在store中,事情就开始复杂起来了。根据Redux的官方文档案例来看,为了实现异步action,你还得需要一个类似于redux-thunk、redux-saga的第三方库来解析你的异步action。
Action.js: 定义异步请求action的地方
1 2 3 4 5 6 7 8
| //这是一个异步action,分发了两个同步action,redux-thunk能够理解它 const fetchGoodsList = url => dispatch => { dispatch(requestGoodsList()); axios.get(url) .then(res=>{ dispatch(receiveGoodsList(res.data)) }) };
|
Reducer.js: 处理同步action
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
| const requestReducer=(state=initialState,action)=>{ switch (action.type) { case REQUEST_GOODSLIST: return Object.assign({},state,{ isFetching: true }); case RECEIVE_GOODSLIST: return Object.assign({},state,{ isFetching:false, goodsList:action.goodsList }); default: return state; } };
App Component : 引入redux store和redux-thunk中间件的地方
import {Provider} from 'react-redux'; import thunkMiddleWare from 'redux-thunk'; import {createStore,applyMiddleware} from 'redux'; //other imports
let store=createStore( rootReducer, //这里要使用中间件,才能够完成异步请求 applyMiddleware( thunkMiddleWare, myMiddleWare,
) ); class App extends React.Component{ render(){ return ( <Provider store={store}> <RootComponent/> </Provider> ) } }
|
Component :进行异步请求的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class *** extends React.Component{ //... componentDidMount(){ this.props.url('your/url'); } //... } const mapDispatchToProps={ fetchGoodsList } export default connect( mapStateToProps, mapDispatchToProps )(GoodsList);
|
使用Hooks-useReducer()和useContext()
使用Redux很累,当然,你可以不使用Redux,直接通过props层层传递,或者使用context都可以。既然出了useReducer,使用到了Redux的思想,总要试着用一下。再麻烦也不会比redux麻烦了(手动狗头)
优点:
1、这里你不需要引入别的任何第三方库了,简简单单地使用React@16.8.0以上的版本就好啦
2、很重要的一点就是——函数式组件,现在React推荐我们这么做,可以基本上代替class写法。
1 2 3
| useReducer(reducer,initialState) useContext(ctxObj) useEffect(effectFunction,[dependencyValues])
|
action.js:
我们还使用redux的思想,编写action
reducer.js:
处理action,不同于redux的reducer,这里我们可以不用提供初始状态
根组件:
Provider提供给子组件context useReducer定义的位置,引入一个reducer并且提供初始状态initialState
子组件:
useContext定义的位置,获取祖先组件提供的context useEffect用于进行异步请求
1.action.js:我们使用action创建函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const REQUEST_GOODSLIST = "REQUEST_GOODSLIST"; const RECEIVE_GOODSLIST = "RECEIVE_GOODSLIST";
//开始请求 const requestGoodsList = () => ({ type: REQUEST_GOODSLIST });
//接收到数据 const receiveGoodsList = json => ({ type: RECEIVE_GOODSLIST, goodsList: json.goodsList, receivedAt: Date.now() });
export { RECEIVE_GOODSLIST, REQUEST_GOODSLIST, receiveGoodsList, requestGoodsList, }
|
2.reducer.js:判断action的类型并进行相应处理,更新state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { RECEIVE_GOODSLIST, REQUEST_GOODSLIST, } from "../..";
export const fetchReducer=(state,action)=>{ switch (action.type) { case REQUEST_GOODSLIST: return Object.assign({},state,{ isFetching: true }); case RECEIVE_GOODSLIST: return Object.assign({},state,{ isFetching:false, goodsList:state.goodsList.concat(action.goodsList) }); default: return state; } };
|
3、根组件(未引用reducer.js的情况下可以这样写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export const EditContext = React.createContext({});
// useReducer与useContext配合注入新的modal显示状态的控制信息************************************** const initialVisiable = false;
const reducer = (visible: boolean, action: string) => { switch (action) { case 'show': return Boolean(1); case 'hide': return Boolean(0);
default: return visible; } };
|
之后需要在根组件的context()中传入相应参数,使得子组件可以获取到相应数据。
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
| const DataListManage: React.FC = props => { const dataManageList = useTestManageList();
//reducer
const [count, dispatch] = useReducer(reducer, initialVisiable);
return ( <PageHeaderLayout> <Card> <EditContext.Provider value={{ visiState: count, visiDispatch: dispatch }} > <DataManageTable {...props} loading={dataManageList.loading} data={dataManageList.data} /> </EditContext.Provider> </Card> </PageHeaderLayout> ); };
|
4、子组件
注意只有被
<***Context.Provider>包裹住的元素才能进行值与函数的传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const onlyForSearch = value => { setSearchList(value); };
<tableContext.Provider value={{ tabledata: dataset, setDataFunc: changeTableData, searchProps: searchState, controlSearchProps: throwStateToContral, searchList: searchList, onlyForSearchResult: onlyForSearch }} > <SearchBoxinTable /> ...
|
5、子组件的子组件
在其中进行声明
1 2 3 4 5 6
| const searchDataSet = useContext(tableContext); ... // console.log(searchDataSet); searchDataSet.setDataFunc(searchResult.data.data.list); searchDataSet.controlSearchProps(true); searchDataSet.onlyForSearchResult({
|
需要注意的是:
1、无论在哪一层级进行context的声明,最终都可以通过provider进行向下的数据传递。
2、context可以进行函数的传递,传递的参数在接收的子元素中进行数据传递回父组件,父组件接收到数据即可进行setState动态数据变更,促使组件render
使用useContext()时候我们不需要使用Consumer了。
但不要忘记export和import上下文对象useEffect()可以看做是class写法的
componentDidMount、componentDidUpdate以及componentWillUnMount三个钩子函数的组合。
当返回了一个函数的时候,这个函数就在compnentWillUnMount生命周期调用。
默认地,传给useEffect的第一个参数会在每次(包含第一次)数据更新时重新调用。当给useEffect()传入了第二个参数(数组类型)的时候,
effect函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。
这相当于我们控制了组件的update生命周期 useEffect()第二个数组为空则意味着仅在componentDidMount周期执行一次
3、不要在一个文件中provider提供完数据后,马上useContext消费数据,可能会取不到值。
Happy Hacking~~