本文是学习了2018年新鲜出炉的React Hooks提案之后,针对异步请求数据写的一个案例。注意,本文假设了:1.你已经初步了解hooks的含义了,如果不了解还请移步官方文档。(其实有过翻译的想法,不过印记中文一直在翻译,就是比较慢啦)2.你使用Redux实现过异步Action(非必需,只是本文不涉及该部分知识而直接使用)3.你听说过axios或者fetch(如果没有,那么想象一下原生js的promise实现异步请求,或者去学习下这俩库)全部代码参见仓库: github | Marckon选择hooks-onlineShop分支以及master分支查看❗ 本文并非最佳实践,如有更好的方法或发现文中纰漏,欢迎指正!前序方案(不想看可以直接跳过)不考虑引入Redux通过学习React生命周期,我们知道适合进行异步请求的地方是componentDidMount钩子函数内。因此,当你不需要考虑状态管理时,以往的方法很简单:class App extends React.Component{ componentDidMount(){ axios.get(’/your/api’) .then(res=>//) }}引入Redux进行状态管理当你决定使用Redux进行状态管理时,比如将异步获取到的数据储存在store中,事情就开始复杂起来了。根据Redux的官方文档案例来看,为了实现异步action,你还得需要一个类似于redux-thunk的第三方库来解析你的异步action。requestAction.js: 定义异步请求action的地方//这是一个异步action,分发了两个同步action,redux-thunk能够理解它const fetchGoodsList = url => dispatch => { dispatch(requestGoodsList()); axios.get(url) .then(res=>{ dispatch(receiveGoodsList(res.data)) })};requestReducer.js: 处理同步actionconst 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 importslet store=createStore( rootReducer, //这里要使用中间件,才能够完成异步请求 applyMiddleware( thunkMiddleWare, myMiddleWare, ));class App extends React.Component{ render(){ return ( <Provider store={store}> <RootComponent/> </Provider> ) }}GoodsList Component :需要进行异步请求的组件class GoodsList extends React.Component{ //… componentDidMount(){ this.props.fetchGoodsList(‘your/url’); } //…}const mapDispatchToProps={ fetchGoodsList}export default connect( mapStateToProps, mapDispatchToProps)(GoodsList);完整代码:branch:master-onlineShop使用Hooks-useReducer()和useContext()总之使用Redux很累,当然,你可以不使用Redux,直接通过props层层传递,或者使用context都可以。只不过本文我们学过了useReducer,使用到了Redux的思想,总要试着用一下。这里你不需要引入别的任何第三方库了,简简单单地使用React@16.7.0-alpha.2版本就好啦很重要的一点就是——函数式组件,现在React推荐我们这么做,可以基本上代替class写法。函数签名useReducer(reducer,initialState)useContext(ctxObj)useEffect(effectFunction,[dependencyValues])概览-你需要编写什么action.js:我们还使用redux的思想,编写actionreducer.js:处理action,不同于redux的reducer,这里我们可以不用提供初始状态根组件:Provider提供给子组件contextuseReducer定义的位置,引入一个reducer并且提供初始状态initialState子组件:useContext定义的位置,获取祖先组件提供的contextuseEffect用于进行异步请求实现1.action.js:我们使用action创建函数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的类型并进行相应处理,更新stateimport { 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.jsimport React,{useReducer} from ‘react’;import {fetchReducer} from ‘..’;//创建并export上下文export const FetchesContext = React.createContext(null);function RootComponent() { //第二个参数为state的初始状态 const [fetchesState, fetchDispatch] = useReducer(fetchReducer, { isFetching: false, goodsList: [] }); return ( //将dispatch方法和状态都作为context传递给子组件 <FetchesContext.Provider value={{fetchesState,dispatch:fetchDispatch}}> //… //用到context的一个子组件 <ComponentToUseContext/> </FetchesContext.Provider> )}4.子组件:引入FetchesContextimport {FetchesContext} from “../RootComponent”;import React, {useContext, useEffect,useState} from ‘react’;import axios from ‘axios’;function GoodsList() { //获取上下文 const ctx = useContext(FetchesContext); //一个判断是否重新获取的state变量 const [reFetch,setReFetch]=useState(false); //具有异步调用副作用的useEffect useEffect(() => { //首先分发一个开始异步获取数据的action ctx.dispatch(requestGoodsList()); axios.get(proxyGoodsListAPI()) .then(res=>{ //获取到数据后分发一个action,通知reducer更新状态 ctx.dispatch(receiveGoodsList(res.data)) }) //第二个参数reFetch指的是只有当reFetch变量值改变才重新渲染 },[reFetch]); return ( <div onScroll={handleScroll}> { //children } </div> )}完整代码参见:branch:hooks-onlineShop目录结构我的目录结构大概这样:src |- actions |- fetchAction.js |- components |-… |- reducers |- fetchReducer.js |- index.js注意点使用useContext()时候我们不需要使用Consumer了。但不要忘记export和import上下文对象useEffect()可以看做是class写法的componentDidMount、componentDidUpdate以及componentWillUnMount三个钩子函数的组合。当返回了一个函数的时候,这个函数就在compnentWillUnMount生命周期调用默认地,传给useEffect的第一个参数会在每次(包含第一次)数据更新时重新调用当给useEffect()传入了第二个参数(数组类型)的时候,effect函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。这相当于我们控制了组件的update生命周期useEffect()第二个数组为空则意味着仅在componentDidMount周期执行一次代码仓库里使用了Mock.js拦截api请求以及ant-design第三UI方库。目前代码比较简陋。