自 React 16.8 之后,推薦使用函數(shù)式組件
Hooks 相關(guān)知識(shí)點(diǎn)參考:React Hooks完全上手指南
以下記錄一些項(xiàng)目中經(jīng)常用到的自定義hooks
useAsync:異步接口請(qǐng)求
import { useState, useCallback } from 'react'
export default const useAsync = (asyncFunc) => {
// 設(shè)置三個(gè)異步邏輯相關(guān)的 state
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
// 定義一個(gè) callback 用于執(zhí)行異步邏輯
const execute = useCallback(() => {
// 請(qǐng)求開始時(shí),設(shè)置 loading 為 true焰雕,清除已有數(shù)據(jù)和 error 狀態(tài)
setLoading(true)
setData(null)
setError(null)
return asyncFunc().then(res => {
// 請(qǐng)求成功,將數(shù)據(jù)寫進(jìn) state识埋,設(shè)置 loading 為 false
setData(res)
setLoading(false)
}).catch(err => {
// 請(qǐng)求失敗树埠,設(shè)置 loading 為 false稽荧,并設(shè)置錯(cuò)誤狀態(tài)
setError(error)
setLoading(false)
})
}, [asyncFunc])
return { execute, loading, data, error }
}
在組件中使用示例
import React from 'react'
import useAsync from './useAsync'
export default function UserList(props) {
// 通過 useAsync 這個(gè)函數(shù),只需要提供異步邏輯的實(shí)現(xiàn)
const {
execute: fetchUsers,
data: users,
loading,
error,
} = useAsync(async () => {
const res = await fetch('http://xxx/xxx')
const json = await res.json
return json.data
})
return (
// 根據(jù)狀態(tài)渲染 UI...
)
}
useUpdate:更新時(shí)執(zhí)行
import { useRef, useEffect } from 'react'
export default function useUpdate(
callback = () => {},
dependences,
initialData = false
) {
const isInitialMount = useRef(true)
useEffect(() => {
// 第一次,也就是mount階段 不執(zhí)行onChange,只有后續(xù)更新的時(shí)候才調(diào)用
// 因?yàn)樵陧撁嬷?一般mount階段會(huì)寫請(qǐng)求數(shù)據(jù)之類的操作
if (isInitialMount.current && !initialData) {
isInitialMount.current = false
} else {
callback()
}
}, dependences)
}
使用示例
useUpdate(() => {
console.log(count)
}, [count])
useForceUpdate:強(qiáng)制更新
import { useReducer, useLayoutEffect, useRef } from 'react'
export default function useForceUpdate() {
// 函數(shù)組件沒有forceUpdate澈歉,用這種方法代替
const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
const resolveRef = useRef(null)
useLayoutEffect(() => {
resolveRef.current && resolveRef.current()
}, [ignored])
const promise = () => {
return new Promise((resolve, reject) => {
forceUpdate()
resolveRef.current = resolve
})
}
return { forceUpdate: promise }
}
使用示例
// 函數(shù)組件沒有forceUpdate展鸡,用這種方法代替
const { forceUpdate } = useForceUpdate()
useEffect(() => {
forceUpdate().then(() => {
// 得等到上一次渲染完成后,才能拿到最新的寬度和高度
chart?.forceFit()
})
}, [count])
useEvent:封裝事件處理函數(shù)
主要有兩個(gè)特點(diǎn)
- 在組件多次
render
時(shí)保持引用一致闷祥。- 函數(shù)內(nèi)始終能獲取到最新的
props
與state
。
import { useLayoutEffect, useCallback, useRef } from 'react'
export default function useEvent(handler) {
const handlerRef = useRef(null)
// 視圖渲染完成后更新 handlerRef.current 指向
// 事件回調(diào)觸發(fā)的時(shí)機(jī)是在視圖完成渲染之后傲诵,這樣可以穩(wěn)定獲取到最新的state與props
useLayoutEffect(() => {
handlerRef.current = handler
})
// 用useCallback包裹凯砍,使得render時(shí)返回的函數(shù)引用一致
return useCallback((...args) => {
const fn = handlerRef.current
return fn(...args)
}, [])
}
使用示例
import { useState } from 'react'
function Demo() {
const [text, setText] = useState('')
// 不管render多少次,onInputChange都是不變的拴竹,做節(jié)流悟衩、防抖等
const onInputChange = useEvent((value) => {
setText(value)
});
return <input onChange={onInputChange} />
}
usePrevious:記錄上一次的值
import { useEffect, useRef } from 'react'
export default function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
使用示例
const [count, setCount] = useState(1)
const preCount = usePrevious(count)
useScroll:監(jiān)聽滾動(dòng)位置
import { useState, useEffect } from 'react'
// 獲取橫向,縱向滾動(dòng)條位置
export default const getPosition = () => {
return {
x: document.body.scrollLeft,
y: document.body.scrollTop,
}
}
export default const useScroll = () => {
// 定一個(gè) position 這個(gè) state 保存滾動(dòng)條位置
const [position, setPosition] = useState(getPosition())
useEffect(() => {
const handler = () => {
setPosition(getPosition())
}
// 監(jiān)聽 scroll 事件栓拜,更新滾動(dòng)條位置
document.addEventListener('scroll', handler)
return () => {
// 組件銷毀時(shí)座泳,取消事件監(jiān)聽
document.removeEventListener('scroll', handler)
}
}, [])
return position
}
返回頂部功能示例
import React, { useCallback } from 'react'
import useScroll from './useScroll'
export default function ScrollTop (props) {
const { y } = useScroll()
const goTop = useCallback(() => {
document.body.scrollTop = 0
}, [])
const style = {
position: 'fixed',
right: '10px',
bottom: '10px',
}
// 當(dāng)滾動(dòng)條位置縱向超過300時(shí),顯示返回頂部按鈕幕与。否則不渲染任何UI
if (y <= 300) return null
return (
<button onClick={goTop} style={style}>
Back to Top
</button>
)
}