zoukankan      html  css  js  c++  java
  • React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案

    本文是学习了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: 处理同步action

    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>
            )
        }
    }

    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写法。

    函数签名

    1. useReducer(reducer,initialState)
    2. useContext(ctxObj)
    3. useEffect(effectFunction,[dependencyValues])

    概览-你需要编写什么

    1. action.js:

      • 我们还使用redux的思想,编写action
    2. reducer.js:

      • 处理action,不同于reduxreducer,这里我们可以不用提供初始状态
    3. 根组件:

      • Provider提供给子组件context
      • useReducer定义的位置,引入一个reducer并且提供初始状态initialState
    4. 子组件:

      • useContext定义的位置,获取祖先组件提供的context
      • useEffect用于进行异步请求

    实现

    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的类型并进行相应处理,更新state

    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

    import 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.子组件:引入FetchesContext

    import {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

    注意点

    1. 使用useContext()时候我们不需要使用Consumer了。但不要忘记exportimport上下文对象
    2. useEffect()可以看做是class写法的componentDidMountcomponentDidUpdate以及componentWillUnMount三个钩子函数的组合。

      • 当返回了一个函数的时候,这个函数就在compnentWillUnMount生命周期调用
      • 默认地,传给useEffect的第一个参数会在每次(包含第一次)数据更新时重新调用
      • 当给useEffect()传入了第二个参数(数组类型)的时候,effect函数会在第一次渲染时调用,其余仅当数组中的任一元素发生改变时才会调用。这相当于我们控制了组件的update生命周期
      • useEffect()第二个数组为空则意味着仅在componentDidMount周期执行一次
    3. 代码仓库里使用了Mock.js拦截api请求以及ant-design第三UI方库。目前代码比较简陋。
  • 相关阅读:
    word设置的密码忘了怎么办?
    Navicat Report Viewer 设置 HTTP 的方法
    如何处理Navicat Report Viewer 报表
    excel密码忘记了怎么办
    Beyond Compare文本比较搜索功能详解
    Popular Cows POJ
    Problem B. Harvest of Apples HDU
    网络流模型整理
    The Shortest Statement CodeForces
    Vasya and Multisets CodeForces
  • 原文地址:https://www.cnblogs.com/10manongit/p/12735971.html
Copyright © 2011-2022 走看看