本文旨在介紹常用的Hook的用途和用法。官方文檔地址
1. State Hook
-
功能: 在組件的頂層調(diào)用
useState來聲明一個(gè) 狀態(tài)變量。-
使用示例
import { useState } from 'react'; // 聲明狀態(tài)變量 const [age, setAge] = useState(28); // 唯一的參數(shù)就是初始化原始值。 const [name, setName] = useState('Taylor'); cosnt [fullName, setFullName] = useState({firstName: 'Tom', lastName: 'James'}) const [todos, setTodos] = useState(() => createTodos()) // 用回調(diào)的形式填入 createTodos 的返回值 const initArr = () => new Array(9).fill(null) const [list, setList] = useState(initArr) // 把函數(shù)本身當(dāng)參數(shù)傳入。如果將函數(shù)傳遞給 useState,React 僅在初始化期間調(diào)用它。當(dāng)組件重新渲染,它也不會(huì)再次運(yùn)行。 // 修改state setAge(age => age + 1) // nextState的更新函數(shù)。只接受待定的 state 作為其唯一參數(shù),并應(yīng)返回下一個(gè)狀態(tài)。 setName('Tom') setFullName({...fullName, firstName: 'Jerry'}) // 更新對(duì)象 // 更新數(shù)組,新增項(xiàng), 修改項(xiàng), 刪除項(xiàng)。 setTodos([ ...todos, { id: id, title, done: false } ]) setTodos(todos.filter(t => t.id !== todoId)) // 通過過濾返回刪除后剩余的項(xiàng)目。 setTodos(tods.map(todo => todo.id !== nextTodo.id ? todo : nextTodo)) // 更新項(xiàng) // 調(diào)用狀態(tài)變量,每次setAge后,會(huì)觸發(fā)頁面的重新渲染。 <div>{age} {fullName.firstName}</div> -
使用 Immer 來簡(jiǎn)化寫法
import {useImmer} from 'use-immer' const [todos, updateTodos] = useImmer([{name: 'Tom', age: 18}]) updateTods(draft => { const obj = draft.find(item => item.name === 'Tom') obj.age = 19 }) -
使用 key 來重置狀態(tài)
-
在 渲染列表 時(shí),你經(jīng)常會(huì)遇到
key屬性。然而,它還有另外一個(gè)用途。通過向組件傳遞不同的key來重置組件的狀態(tài)。const [count, setCount] = useState(0) const [version, setVersion] = useState(0) setVersion(version + 1) // count更新時(shí)視圖都會(huì)重新渲染,version變化時(shí),不管count有沒有變化視圖都會(huì)重新渲染 <div key={version}>{count}</div>
-
-
存儲(chǔ)前一次渲染的信息
- 在極為罕見的情況下,在組件渲染時(shí)調(diào)用
set函數(shù)來基于已經(jīng)渲染的值更新狀態(tài)。
- 在極為罕見的情況下,在組件渲染時(shí)調(diào)用
-
set函數(shù)執(zhí)行時(shí)機(jī),是遇見set函數(shù)就推進(jìn)執(zhí)行隊(duì)列,要當(dāng)前作用域內(nèi)容執(zhí)行之后才會(huì)進(jìn)行執(zhí)行隊(duì)列進(jìn)行更新狀態(tài)值
const [count, setCount] = useState(0) const handleClick = () => { // log(count) 0 setCount(2) // log(count) 0 setTimeout(() => { // log(count) 0 }) } // 解決辦法, 使用臨時(shí)變量 const handleClick = () => { const nextCount = count + 1 setCount(nextCount) // log(count) 0 // log(nextCount) 1 }
在組件的頂層作用域調(diào)用
useReducer以創(chuàng)建一個(gè)用于管理狀態(tài)的 reducer。使用場(chǎng)景:批量更新
state時(shí)候使用。-
useReducer的三個(gè)參數(shù)
import {useReducer} from 'react' const [state, dispatch] = useReducer(reducer, initialArg, init?)- reducer: 用于更新 state 的純函數(shù)。參數(shù)為 state 和 action,返回值是更新后的 state。state 與 action 可以是任意合法值。
-
initialArg:用于初始化 state 的任意值。初始值的計(jì)算邏輯取決于接下來的init參數(shù)。 -
可選參數(shù)
init:用于計(jì)算初始值的函數(shù)。如果存在,使用init(initialArg)的執(zhí)行結(jié)果作為初始值,否則使用initialArg。
dispatch函數(shù):useReducer返回的dispatch函數(shù)允許你更新 state 并觸發(fā)組件的重新渲染-
使用示例:
import {useReducer, useState} from 'react' cosnt initData = [ {id: 1, name: 'Tom', age: 18}, {id: 2, name: 'Jerry', age: 16} ] /** * 聲明reducer函數(shù) * 兩個(gè)參數(shù),list是當(dāng)前的state, action 可以是任意合法值,包含了要操作的類型和需要跟新的數(shù)據(jù)。 * 返回值是 更新后的 state */ const stuReducer (list, action) { switch (action.type) { case 'add': { // 往數(shù)組中添加數(shù)據(jù) return [ ...list, {id: action.id, name: action.name, age: action.age} ] } case 'update': { // 更新數(shù)組中一項(xiàng)的數(shù)據(jù) return list.map(item => item.id === action.id ? ({id: action.id, name: action.name, age: action.age}) : item) } case 'delete': { // 過濾掉要?jiǎng)h除的數(shù)據(jù) return list.filter(item => item.id !== aciton.id) } default: { throw Error ('沒有找到對(duì)應(yīng)的' + action.type) } } } const Demo = () => { /** * useReducer三個(gè)參數(shù) * 第一個(gè)參數(shù):stuReducer 用于更新 state 的純函數(shù) * 第二個(gè)參數(shù):initData 用于初始化 state 的任意值。 * 補(bǔ)?。旱诙€(gè)參數(shù)initData也可以是一個(gè)函數(shù)的返回值比如formatArr(initData)。但是這樣的話每次更新state時(shí)候formatArr函數(shù)都會(huì)被調(diào)用,請(qǐng)把formatArr函數(shù)本身作為第三個(gè)參數(shù)傳入useReducer中。例如:useReducer(stuReducer, initData, formatArr) * 還有第三個(gè)可選參數(shù),這里省略了。是用于計(jì)算初始值的函數(shù)。 * */ const [list, dispatch] = useReducer(stuReducer, initData) const oprateList = (type) { // 根據(jù)不同的type值調(diào)用reducer函數(shù)返回對(duì)應(yīng)更新后的state. dispatch({ type: type, id: 'xx', name: 'xx', age: 99 }) // dispatch之后打印一下list,發(fā)現(xiàn)還是更新之前的數(shù)據(jù),這里的原理與 useState 相同。這是因?yàn)?state 的行為和快照一樣。更新 state 會(huì)使用新的值來對(duì)組件進(jìn)行重新渲染,但是不會(huì)改變當(dāng)前執(zhí)行的事件處理函數(shù)里面 state 的值。 } return <div>要渲染的內(nèi)容以及操作的按鈕內(nèi)容</div> } -
reducer 和初始化函數(shù)運(yùn)行了兩次
2. Context Hook
-
向組件樹深層傳遞數(shù)據(jù)。與prop的父子組件組件傳值相比,context可跨代進(jìn)行數(shù)據(jù)傳遞。并能更新傳遞的數(shù)據(jù)。
-
使用示例
// createContext.ts // 第一步要?jiǎng)?chuàng)建context import {createContext} from 'react' const ThemeContext = createContext(null) // 也可以默認(rèn)主體色 light createContext('light') export default ThemeContext// 組件中調(diào)用context import {useContext, useState} from 'react' import ThemeContext from './createContext' const App = () => { const [theme, setTheme] = useState('dark') /** * value='dark' 修改了 themeContext的值 * 如果這里的 value值 不輸入,則取的是 createContext的默認(rèn)值。 * 下面是通過使用state來初始化和更新 themeContext的值的示例。 * <ThemeContext.Provider value={theme}><Form /></ThemeContext.Provider> * setTheme('light') 會(huì)更新themeContext的值 */ return ( <ThemeContext.Provider value='dark'><Form /></ThemeContext.Provider> ) } const Form = () => { const theme = useContext(ThemeContext) const className = `box-${theme}` // box-dark return <div className={className}></div> }
-
補(bǔ)丁: 通過在 provider 中使用不同的值包裝樹的某個(gè)部分,可以覆蓋該部分的 context。
<ThemeContext.Provider value="dark"> ... <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> ... </ThemeContext.Provider>
3. Ref Hook
-
-
引用一個(gè)不需要渲染的值,通過ref操作
DOM,儲(chǔ)存定時(shí)器 interval ID。const ref = useRef(initialValue) # initialValue:ref 對(duì)象的 current 屬性的初始值??梢允侨我忸愋偷闹?。這個(gè)參數(shù)在首次渲染后被忽略。 改變Ref不會(huì)觸發(fā)頁面的重新渲染
-
使用示例
import {useRef} from 'react' const MyApp = () => { const inputRef = useRef(null) // 引用input的元素 const intervalRef = useRef(null) const handleClick = () => { const intervalRef = setInterval(() => {}, 10) // 清除定時(shí)器 clearInterval(intervalRef.current) // 引用dom inputRef.current.focus() } return <> <input ref={inputRef} /> </> }
-
補(bǔ)丁: 無法獲取自定義組件的 ref。使用
forwardRef來解決。-
- 能讓你自定義由 ref 暴露出來的句柄。是
forwardRef的優(yōu)化補(bǔ)充。
- 能讓你自定義由 ref 暴露出來的句柄。是
4. Effect Hook
-
-
允許你 將組件與外部系統(tǒng)同步。
useEffect(setup, dependencies?)setup:處理 Effect 的函數(shù)。setup 函數(shù)選擇性返回一個(gè) 清理(cleanup) 函數(shù)。當(dāng)組件被添加到 DOM 的時(shí)候,React 將運(yùn)行 setup 函數(shù)。在每次依賴項(xiàng)變更重新渲染后,React 將首先使用舊值運(yùn)行 cleanup 函數(shù)(如果你提供了該函數(shù)),然后使用新值運(yùn)行 setup 函數(shù)。在組件從 DOM 中移除后,React 將最后一次運(yùn)行 cleanup 函數(shù)。-
可選
dependencies:setup代碼中引用的所有響應(yīng)式值的列表。<font color='red'>響應(yīng)式值包括 props、state 以及所有直接在組件內(nèi)部聲明的變量和函數(shù)</font>。如果你的代碼檢查工具 配置了 React,那么它將驗(yàn)證是否每個(gè)響應(yīng)式值都被正確地指定為一個(gè)依賴項(xiàng)。依賴項(xiàng)列表的元素?cái)?shù)量必須是固定的,并且必須像[dep1, dep2, dep3]這樣內(nèi)聯(lián)編寫。React 將使用Object.is來比較每個(gè)依賴項(xiàng)和它先前的值。如果省略此參數(shù),則在每次重新渲染組件之后,將重新運(yùn)行 Effect 函數(shù)。-
如果指定了依賴項(xiàng),則 Effect 在 初始渲染后以及依賴項(xiàng)變更的重新渲染后 運(yùn)行。
useEffect(() => { // ... }, [a, b]); // 如果 a 或 b 不同則會(huì)再次運(yùn)行
-
-
- 如果你的 Effect 確實(shí)沒有使用任何響應(yīng)式值,則它僅在 **初始渲染后** 運(yùn)行。
```jsx
useEffect(() => {
// ...
}, []); // 不會(huì)再次運(yùn)行(開發(fā)環(huán)境下除外)
```
- 如果完全不傳遞依賴數(shù)組,則 Effect 會(huì)在組件的 **每次單獨(dú)渲染(和重新渲染)之后** 運(yùn)行。
```jsx
useEffect(() => {
// ...
}); // 總是再次運(yùn)行
```
-
使用場(chǎng)景
-
使用示例:
-
請(qǐng)求數(shù)據(jù)
mport { useState, useEffect } from 'react'; import { fetchBio } from './api.js'; export default function Page() { const [person, setPerson] = useState('Alice'); const [bio, setBio] = useState(null); useEffect(() => { let ignore = false; setBio(null); fetchBio(person).then(result => { if (!ignore) { setBio(result); } }); return () => { ignore = true; }; }, [person]); -
定時(shí)器
cosnt [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1) }, 1000) return () => clearInterval(timer) }, [])
-
-
-
useLayoutEffect是useEffect的一個(gè)版本,在瀏覽器重新繪制屏幕之前觸發(fā)。 可能會(huì)影響性能。<font color="red">謹(jǐn)慎使用</font> - 在瀏覽器重新繪制屏幕前計(jì)算布局。
-
-
- 是為 CSS-in-JS 庫的作者特意打造的。除非你正在使用 CSS-in-JS 庫并且需要注入樣式,否則你應(yīng)該使用
useEffect或者useLayoutEffect。
- 是為 CSS-in-JS 庫的作者特意打造的。除非你正在使用 CSS-in-JS 庫并且需要注入樣式,否則你應(yīng)該使用
5. 性能 Hook
-
useMemo- 它在每次重新渲染的時(shí)候能夠緩存計(jì)算的結(jié)果。
-
useCallback- 是一個(gè)允許你在多次渲染中緩存函數(shù)的 React Hook。
-
useTransition- 是一個(gè)幫助你在不阻塞 UI 的情況下更新狀態(tài)的 React Hook。
- 它允許你在某些狀態(tài)更新時(shí)實(shí)現(xiàn)過渡效果,即這些更新可以有不同的優(yōu)先級(jí),從而可以延遲一些不那么重要的更新,以便更快速地響應(yīng)更關(guān)鍵的用戶交互。這個(gè)Hook的主要作用是在狀態(tài)更新時(shí),提供了一種方式來“標(biāo)記”某些更新為“過渡”,這意味著這些更新可能會(huì)延遲,以便React可以先處理其他更高優(yōu)先級(jí)的更新,如用戶的點(diǎn)擊事件。
- 常用于優(yōu)化視圖切換時(shí)的用戶體驗(yàn)。例如,當(dāng)某個(gè)組件的渲染特別耗時(shí),如Movie組件,如果在渲染該組件期間頁面的UI被阻塞,用戶會(huì)感覺頁面卡頓。通過使用useTransition,可以優(yōu)化這種情況下的用戶體驗(yàn)。此外,useTransition還可以幫助你控制過渡效果的持續(xù)時(shí)間和延遲時(shí)間等參數(shù),使得用戶可以看到過渡效果,而不是直接看到新的組件,從而提高用戶體驗(yàn)。
-
useDeferredValue- 可以讓你延遲更新 UI 的某些部分。
- 可以將
useDeferredValue作為性能優(yōu)化的手段。當(dāng)你的 UI 某個(gè)部分重新渲染很慢、沒有簡(jiǎn)單的優(yōu)化方法,同時(shí)你又希望避免它阻塞其他 UI 的渲染時(shí),使用useDeferredValue很有幫助。
6. 資源 Hook
7. 其他 Hook
- 使用
useDebugValue自定義 React 開發(fā)者工具為自定義 Hook 添加的標(biāo)簽。 - 使用
useId將唯一的 ID 與組件相關(guān)聯(lián),其通常與可訪問性 API 一起使用。 - 使用
useSyncExternalStore訂閱外部 store。
8. 自定義 Hook
- 開發(fā)者可以 自定義 Hook 作為 JavaScript 函數(shù)。