自 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
)
複製代碼
本文完~
若有問題,歡迎指出~