自 React Hooks 16.8.0 后带来了 React hooks 这一特性。这一特性在没有破坏性的更新下为咱们带来了更加舒爽的开发方式。过去咱们经常因providers,consumers,高阶组件,render props 等造成“嵌套地狱”。尽管 Class Component 在某种程度上为咱们提供了更方便的写法以及生命周期,但同时也带来了一些很差的地方。例如难以理解的 class 内部原理、难以测试的声明周期。而 React Hooks 为咱们提供了一种 Function Component 的写法,让咱们用更少的代码写出更加优雅、易懂的代码。本文不作 React Hooks API的讲述,若有不懂,请移步 Hooks 简介javascript
在开发代码时,咱们发送后端请求后接受到的数据,须要使用try/catch来捕获错误。而每次捕获出的错误可能须要打印出来以检测bug。这样咱们每次都会写一样的代码,这样在开发过程当中很不友好。同时有些同窗不习惯使用 try/catch 来捕获错误,这就可能形成不可预计的问题。html
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,虽然不是大问题,可是重复代码写多了会以为难受...。下面看第二个功能。java
在实际的业务场景中,咱们向后端发送请求时,每每伴随着用户点击屡次,可是只能发送一次请求的问题,这时咱们须要手动加锁。而且在不少场景中咱们须要知道请求状态来为页面设置loading。例如:react
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来解决问题。api
custom hooks 解决的问题async
因此咱们须要在 custom hooks 中发送请求、暴露出请求后的值、暴露 loading 状态、以及用户可能须要屡次请求,这就须要暴露一个勾子。在发生请求错误时可能须要作某些操做,因此还须要暴露在错误时回调的勾子函数。ide
是否当即请求并接受初始化返回值函数
业务咱们并不但愿初始化的是否当即发送请求。 而且可以有初始化的返回值post
支持泛型
在TS中,开发者但愿可以自定义请求的参数类型,以及请求结果的类型
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;
复制代码
根据最初的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] = useFetch<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 来将状态以及发送请求封装在一块儿。可以让咱们写更少的代码。
也许有些请求不须要关注请求状态
// 解构赋值、空着就好
const [list, , getList] = useFetch<ListInfo, ParamsType>(
getWithDraw,
state,
{ list: [], total: 0 },
false
)
复制代码
本文完~
若有问题,欢迎指出~