React Hooks 之 useFetch

栏目: IOS · Android · 发布时间: 6年前

内容简介:自 React Hooks 16.8.0 后带来了 React hooks 这一特性。这一特性在没有破坏性的更新下为我们带来了更加舒爽的开发方式。过去我们常常因providers,consumers,高阶组件,render props 等形成“嵌套地狱”。尽管 Class Component 在某种程度上为我们提供了更方便的写法以及生命周期,但同时也带来了一些不好的地方。例如难以理解的 class 内部原理、难以测试的声明周期。而 React Hooks 为我们提供了一种 Function Componen

自 React Hooks 16.8.0 后带来了 React hooks 这一特性。这一特性在没有破坏性的更新下为我们带来了更加舒爽的开发方式。过去我们常常因providers,consumers,高阶组件,render props 等形成“嵌套地狱”。尽管 Class Component 在某种程度上为我们提供了更方便的写法以及生命周期,但同时也带来了一些不好的地方。例如难以理解的 class 内部原理、难以测试的声明周期。而 React Hooks 为我们提供了一种 Function Component 的写法,让我们用更少的代码写出更加优雅、易懂的代码。本文不做 React Hooks API的讲述,如有不懂,请移步Hooks 简介

发送服务端请求所面临的问题

1. try / catch问题

在开发代码时,我们发送后端请求后接受到的数据,需要使用try/catch来捕获错误。而每次捕获出的错误可能需要打印出来以检测bug。这样我们每次都会写同样的代码,这样在开发过程中很不友好。同时有些同学不习惯使用 try/catch 来捕获错误,这就可能造成不可预计的问题。

import React, { useCallback, useReducer, useEffect } from 'react'
import { TimeNumberType, PageType } from 'common/constant/interface'

type ParamsType = PageType & TimeNumberType

const reducer = (state: ParamsType, action: Actions) => {
  const { payload } = action
  return { ...state, ...payload }
}
const postListData = (params: ParamsType) => post('/network/api/test/getlist', params)
const initialParams = {
  pageSize: 10,
  pageNumber: 1,
  startTime: 0,
  endTime: 0
}

const ListComponent = () => {
  const [params, dispatch] = useReducer(reducer, initialState)
  const getList = async () => {
    // try catch
    try {
      const res = await postListData(params)
      console.log(res)
    } catch (err) {
      console.error(err)
    }
  }

  useEffect(() => {
    getList()
  }, [params])
}
复制代码

demo中展示了在业务场景中发送请求的场景,当发送请求多了之后我们会每次手动try / catch,虽然不是大问题,但是重复代码写多了会觉得难受...。下面看第二个功能。

2. 请求状态

在实际的业务场景中,我们向后端发送请求时,往往伴随着用户点击多次,但是只能发送一次请求的问题,这时我们需要手动加锁。并且在很多场景中我们需要知道请求状态来为页面设置loading。例如:

import React, { useCallback, useReducer, useEffect } from 'react'
import { TimeNumberType, PageType } from 'common/constant/interface'
import { DateRangePicker, Table } from 'UI'

type ParamsType = PageType & TimeNumberType

const TIME = Symbol('time')
const PAGE = Symbol('page')
const reducer = (state: ParamsType, action: Actions) => {
  const { payload } = action
  return { ...state, ...payload }
}
const postListData = (params: ParamsType) => post('/network/api/test/getlist', params)
const initialParams = {
  pageSize: 10,
  pageNumber: 1,
  startTime: 0,
  endTime: 0
}

const ListComponent = () => {
  const [params, dispatch] = useReducer(reducer, initialState)
  const [loading, setLoading] = useState(false)
  const [list, setList] = useState({})
  
  const getList = async () => {
    // loading is true
    if (loading) return
    // set loading status
    setLoading(true)
    // try catch
    try {
      const res = await postListData(params)
      setList(res)
      setLoading(false)
    } catch (err) {
      console.error(err)
      setLoading(false)
    }
  }

  useEffect(() => {
    getList()
  }, [params])
  
  return (
      <div style={{ marginBottom: '20px' }}>
        <DateRangePicker
          onChange={handleDateChange}
        />
      <Table
        onPageChange={(pageNumber: number) => {
          dispatch({ payload: { pageNumber }, type: PAGE })
        }}
        list={list}
        // 数据是否正在加载,以此来判断是否需要展示loading
        loading={loading}
      />
    </div>
  )
}
复制代码

demo中展示了日期组件以及包含有分页器的 Table组件,当日期发生变更,或者分页器发生变更时,我们需要dispatch来更新请求参数,从而发送请求。在发送请求时如果正在请求,则忽略,而不在请求时需要手动加锁,来防止多次请求。

同时Table需要根据请求状态来判断是否需要展示loading。

解决问题

基于以上的问题,我们能否通过 Hooks 来封装一个 custom hooks来解决问题。

1. 明确目标

custom hooks 解决的问题

  • 解决每个函数都要统一写try/catch的流程
  • 解决发送请求需要手动加锁防止多次重复请求的痛点
  • 不需要在手动useState loading,直接获取loading值

所以我们需要在 custom hooks 中发送请求、暴露出请求后的值、暴露 loading 状态、以及用户可能需要多次请求,这就需要暴露一个勾子。在发生请求错误时可能需要做某些操作,所以还需要暴露在错误时回调的勾子函数。

是否立即请求并接受初始化返回值

业务我们并不希望初始化的是否立即发送请求。 并且能够有初始化的返回值

支持泛型

在TS中,开发者希望能够自定义请求的参数类型,以及请求结果的类型

2. 定义函数

useFetch 函数

import { useState, useEffect } from "react";

/**
 * 1. 解决每个函数都要统一写try/catch的流程
 * 2. 解决发送请求需要手动加锁防止多次重复请求的痛点
 * 3. 不需要在手动useState loading了~,直接获取fetching值
 * 4. (甚至在参数发生变化时只需要传入更改的参数就OK)已删除
 * @param getFunction 发送请求的函数
 * @param params 参数
 * @param initRes 初始化值
 * @param execute 是否立即执行请求函数
 */

// R, P支持泛型
function UseFetch<R, P>(
  getFunction: any,
  params: P,
  initRes?: R,
  execute: boolean = true
): [
  R,
  boolean,
  (params?: Partial<P>) => void,
  (fn?: (err: any) => void) => void
] {
  type ErrorFunction = ((fn?: (err: any) => void) => void) | null;
  const [res, setRes] = useState(initRes as R);
  const [fetching, setFetch] = useState(false);
  const [failed, setFailed] = useState<ErrorFunction>(null);

  // 参数也许并不是每次都完整需要 Partial<P>
  const fetchData: (params?: Partial<P>) => void = async (params?: any) => {
    if (fetching) return;
    setFetch(true);
    try {
      setRes(await getFunction(params));
      setFetch(false);
    } catch (err) {
      console.error(err);
      setFetch(false);
      failed && failed(err);
    }
  };

  const setError: ErrorFunction = fn => fn && setFailed(fn);

  // 首次执行只请求一次
  useEffect(() => {
    execute && fetchData(params);
  }, []);

  /**
   * res 返回的数据
   * fetching 是否在请求中
   * fetchData 手动再次触发请求
   * setError 当发生请求错误时,需要执行的回掉函数
   */
  return [res, fetching, fetchData, setError];
}

const useFetch = UseFetch;

export default useFetch;


复制代码

3. 如何使用

根据最初的demo我们改造一下代码

import React, { useCallback, useReducer, useEffect } from 'react'
import { TimeNumberType, PageType } from 'common/constant/interface'
import { DateRangePicker, Table } from 'UI'
// 导入 useFetch
import { useFetch } from 'custom-hooks' 

type ParamsType = PageType & TimeNumberType
type ListInfo = {list: Array<any>, total: number}

const TIME = Symbol('time')
const PAGE = Symbol('page')
const reducer = (state: ParamsType, action: Actions) => {
  const { payload } = action
  return { ...state, ...payload }
}
const postListData = (params: ParamsType) => post('/network/api/test/getlist', params)
const initialParams = {
  pageSize: 10,
  pageNumber: 1,
  startTime: 0,
  endTime: 0
}

const ListComponent = () => {
  const [params, dispatch] = useReducer(reducer, initialState)
  
  const [list, loading, getList] = useLoading<ListInfo, ParamsType>(
    getWithDraw,
    state,
    { list: [], total: 0 },
    false
  )

  useEffect(() => {
    getList()
  }, [params])
  
  return (
      <div style={{ marginBottom: '20px' }}>
        <DateRangePicker
          onChange={handleDateChange}
        />
      <Table
        onPageChange={(pageNumber: number) => {
          dispatch({ payload: { pageNumber }, type: PAGE })
        }}
        list={list}
        // 数据是否正在加载,以此来判断是否需要展示loading
        loading={loading}
      />
    </div>
  )
}
复制代码

对比代码我们可以看到中间的请求的代码被我们干掉了,使用 useFetch 来将状态以及发送请求封装在一起。能够让我们写更少的代码。

同时 useFetch的第3个参数当传入的为 null 时,可以模拟请求发送错误,这样我们可以在开发时做兜底方案。


以上所述就是小编给大家介绍的《React Hooks 之 useFetch》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Data Structures and Algorithms in Python

Data Structures and Algorithms in Python

Michael T. Goodrich、Roberto Tamassia、Michael H. Goldwasser / John Wiley & Sons / 2013-7-5 / GBP 121.23

Based on the authors' market leading data structures books in Java and C++, this book offers a comprehensive, definitive introduction to data structures in Python by authoritative authors. Data Struct......一起来看看 《Data Structures and Algorithms in Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换