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方库。目前代码比较简陋。
  • 相关阅读:
    d3 中exit() remove()正确工作的方式
    理解callback function in javascript
    关于AJAX中函数的执行顺序
    闭包允许内层函数引用父函数中的变量,但是该变量是最终值
    操作系统 庞丽萍 习题九
    关于vector的内存释放问题
    gcc命令中参数c和o混合使用的详解[转载]
    模板函数(template function)出现编译链接错误(link error)之解析
    [转载]config文件的一个很好的实现
    new 等于 malloc加构造函数
  • 原文地址:https://www.cnblogs.com/10manongit/p/12735971.html
Copyright © 2011-2022 走看看