React中useState和useEffect的用法詳解
之前在不討論 React Hooks 和組件生命周期的基礎(chǔ)上介紹了函數(shù)組件和類組件的差別,現(xiàn)在介紹一個(gè)為函數(shù)組件而生的知識(shí)點(diǎn),即:React Hooks。Hooks 是函數(shù),在 React 16.8 正式發(fā)布,它對(duì)類組件沒有影響。
類組件的功能強(qiáng)大,能擁有自己的state,有生命周期,開發(fā)人員能根據(jù)需求在特定的生命周期中執(zhí)行自己要想的操作,如:發(fā)送Ajax請(qǐng)求等。類組件功能雖強(qiáng),但它存在如下問題:
1.必須時(shí)常關(guān)注 this 關(guān)鍵字的指向,這對(duì)初學(xué)者而言不是一件容易的事。
2.相同的生命周期函數(shù)在類組件中最多定義一個(gè),這導(dǎo)致彼此無關(guān)的邏輯代碼被揉雜在同一生命周期函數(shù)中。
3.不同的生命周期函數(shù)可能包含相同的代碼,最常見便是 componentDidMount 和 componentDidUpdate。
Hooks 發(fā)布之后,函數(shù)組件能擁有自己的 state,也能感知到組件被銷毀和 DOM 被繪制到屏幕上的時(shí)機(jī),但是沒有類組件存在的那三個(gè)問題。React提供了很多內(nèi)置的Hooks,在本文介紹 useState 和 useEffect。
useState
它是一個(gè)與狀態(tài)管理相關(guān)的hook,它讓函數(shù)組件擁有狀態(tài),是最常用的Hooks之一,類型定義如下:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]; function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
從類型定義可以看出,useState 有兩個(gè)重載,分別是傳參數(shù)和不傳參數(shù),不論是否傳參數(shù),useState 都返回一個(gè)長(zhǎng)度為 2 的數(shù)組,數(shù)組的第一個(gè)位置是狀態(tài),它可以是任何數(shù)據(jù)類型,類型參數(shù) S 用于注釋它的類型,第二個(gè)位置是一個(gè)用于更新狀態(tài)的函數(shù),為了語(yǔ)言上的便利本小節(jié)將該函數(shù)記為 setState。接下來,介紹 useState 的基本用法。
當(dāng) useState 的參數(shù)不是函數(shù)
此時(shí),useState 的參數(shù)作為狀態(tài)的初始值,如果沒有傳參數(shù),那么狀態(tài)的初始值為 undefined。用法如下:
import React, { useState } from 'react'
export function UseStateWithoutFunc() {
const [name, setName] = useState<string>('何遇')
const [age, setAge] = useState<number>()
function onChange() {
setName(Math.random() + '') // 修改name
setAge(Math.random()) // 修改age
}
return (
<>
<div>姓名: {name}</div>
<div>年齡: {age === undefined ? '未知' : age}</div>
<button onClick={onChange}>click</button>
</>
)
}UseStateWithoutFunc 組件有 name 和 age 這兩個(gè)狀態(tài),name 只能是 string 類型,初始值為'何遇',age 的數(shù)據(jù)類型是 number 或 undefined,初始值為undefined。
當(dāng) useState 的參數(shù)是函數(shù)
此時(shí),函數(shù)的返回值是狀態(tài)的初始值。某些時(shí)候,狀態(tài)的初始值要經(jīng)過計(jì)算才能得到,此時(shí)推薦將函數(shù)作為 useState 的參數(shù),該函數(shù)只在組件初始渲染執(zhí)行一次。用法如下:
function UseStateWithFunc() {
const [count, setCount] = useState<number>(() => {
// 這個(gè)函數(shù)只在初始渲染的時(shí)候執(zhí)行,后續(xù)的重新渲染不再執(zhí)行
return Number(localStorage.getItem('count')) || 0
})
function onChange() {/** todo*/}
return (
<>
<div>count: {count}</div>
<button onClick={onChange}>click</button>
</>
)
}上述 useState 的參數(shù)是函數(shù),count 的初始值為該函數(shù)的返回值,數(shù)據(jù)類型是 number。
修改狀態(tài)的值
延用上述代碼中的 setCount,修改狀態(tài)有兩種方式。用法如下:
// 用法一
setCount((count) => {
return count + 1
})
// 用法二
setCount(0)如果 setCount 的參數(shù)是函數(shù),那么 count 現(xiàn)在的值將以參數(shù)的形式傳給該函數(shù),函數(shù)的返回值用于更新狀態(tài)。如果 setCount 的參數(shù)不是函數(shù),那么該參數(shù)將用于更新狀態(tài)。修改狀態(tài)只能使用與它相關(guān)的 setState ,且必須滿足 Immutability 原則,狀態(tài)值發(fā)生變更將導(dǎo)致組件重新渲染,重新渲染時(shí),useState 返回的第一個(gè)值始終是狀態(tài)最新的值,不會(huì)重置為初始值。
目前已介紹完 useState 的基本用法,觀察下面這段更復(fù)雜的代碼,分析瀏覽器打印的結(jié)果。
function UseStateAdvanceDemo() {
// count 的初始值為 0
const [count, setCount] = useState<number>(0)
const onClick = () => {
setCount((prevCount) => prevCount + 1) // 將count在原來的基礎(chǔ)上加1
setTimeout(() => {
console.log(count) // 分析瀏覽器打印的結(jié)果
}, 1000)
}
return <button onClick={onClick}>打開開發(fā)者工具再點(diǎn)擊</button>
}如果你理解了React 的函數(shù)組件中介紹的知識(shí),那么能輕而易舉的分析出瀏覽器打印的值為0而不是1。再?gòu)?qiáng)調(diào)一次,在函數(shù)組件中取 state 和 props 拿到的都是本次渲染的值,在本次渲染范圍內(nèi),props 和 state 始終不變。在上述代碼中,調(diào)用 setCount 會(huì)導(dǎo)致組件重新渲染,在下一次渲染時(shí) count 的值為 1,但 console.log(count) 打印的是本次渲染時(shí) count 的值,所以結(jié)果為 0。
useEffect
useEffect 是除 useState 之外另一個(gè)常用的 Hook,它比 useState 的理解難度更大,但只要你明白函數(shù)組件每次渲染都有它自己的 state 和 props,那么理解useEffect 將變得容易。使用 useEffect 能讓開發(fā)人員知道 DOM 什么時(shí)候被繪制到了屏幕,組件什么時(shí)候被銷毀了,有些開發(fā)人員認(rèn)為它是類組件 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期函數(shù)的結(jié)合,但實(shí)際上函數(shù)組件沒有與類組件類似的生命周期概念。
useEffect 類型定義如下:
type EffectCallback = () => (void | Destructor); function useEffect(effect: EffectCallback, deps?: DependencyList): void;
從類型定義可以看出,useEffect 最多接受兩個(gè)參數(shù),第一個(gè)參數(shù)是函數(shù),可以有返回值,之后將該函數(shù)稱為 effect;第二個(gè)參數(shù)非必填,是個(gè)數(shù)組,它是 effect 的依賴,稱為 deps。deps 用于確定 effect 在本次渲染是否執(zhí)行,如果執(zhí)行,那么在瀏覽器將 DOM 繪制到屏幕之后執(zhí)行,可以將 Ajax 請(qǐng)求,訪問 DOM 等操作放在effect 中,它不會(huì)阻塞瀏覽器繪制。函數(shù)組件可以多次使用 useEffect,每使用一次就定義一個(gè) effect,這些 effect 的執(zhí)行順序與它們被定義的順序一致,建議將不同職責(zé)的代碼放在不同的 effect 中。接下來從 effect 的清理工作和它的依賴這兩個(gè)方面介紹 useEffect。
effect沒有清理工作
effect 沒有清理工作就意味著它沒有返回值,代碼如下:
function EffectWithoutCleanUp() {
const [name, setTitle] = useState<string>('何遇')
useEffect(() => {
document.title = name
})
return (
// something
)
}上述代碼定義了一個(gè) effect,它的作用是將 document.title 設(shè)置成本次渲染時(shí) name 的值。
effect 有清理工作
effect 的清理工作指 effect 返回的函數(shù),該函數(shù)在組件重新渲染后和組件卸載時(shí)被調(diào)用,下面的代碼定義了一個(gè)有清理工作的 effect。
function EffectWithCleanUp() {
const [name, setTitle] = useState<string>('何遇')
useEffect(() => {
const onBodyClick = () => {/** todo */}
document.body.addEventListener('click', onBodyClick)
// 在返回的函數(shù)中定義與該effect相關(guān)的清理工作
return () => {
document.body.removeEventListener('click', onBodyClick)
}
})
return (
// something
)
}上述 effect 在 DOM 被繪制到界面之后給 body 元素綁定 click 事件,組件重新渲染之后將上一次 effect 綁定的 click 事件解綁。該 effect 在組件首次渲染和之后的每次重新渲染都將執(zhí)行,如果組件的狀態(tài)更新頻繁,那么組件重新渲染也會(huì)很頻繁,這導(dǎo)致 body 頻繁綁定 click 事件又解綁 click事件。是否有辦法使組件只在首次渲染時(shí)給 body 綁定事件呢?當(dāng)然有。
effect 的依賴
前面兩個(gè)示例定義的 effect 沒有指明依賴,因此組件每一輪渲染都會(huì)執(zhí)行它們。下面的代碼讓組件只在首次渲染時(shí)給 body 綁定事件,代碼如下:
useEffect(() => {
const onBody = () => {/** todo */}
document.body.addEventListener('click', onBody)
return () => {
// 在組件卸載時(shí)將事件解綁
document.body.removeEventListener('click', onBody)
}
}, [])給 useEffect 第二個(gè)參數(shù)傳空數(shù)據(jù)意味著 effect 沒有依賴,該 effect 只在組件初始渲染時(shí)執(zhí)行,它的清理工作在組件卸載時(shí)執(zhí)行。對(duì)于綁定 DOM 事件而言這是一件好事,它可以防止事件反復(fù)綁定和解綁,但問題是,如果在事件處理程序中訪問組件的 state 和 props,那么只能拿到它們的初始值,拿不到最新的值。是否有辦法讓 effect 始終拿到 state 和 props 最新的值呢?有。
給 effect 傳遞依賴項(xiàng),React 會(huì)將本次渲染時(shí)依賴項(xiàng)的值與上一次渲染時(shí)的值進(jìn)行淺對(duì)比,如果結(jié)論是它們其中之一有變化,那么該 effect 會(huì)被執(zhí)行,否則不會(huì)執(zhí)行。為了讓 effect 拿到它所需 state 和 props 的最新值,effect 中所有要訪問的外部變量都應(yīng)該作為依賴項(xiàng)放在 useEffect 第二個(gè)參數(shù)中。代碼如下:
useEffect(() => {
const onBody = () => {
console.log(name) // 始終得到最新的name值
}
document.body.addEventListener('click', onBody)
return () => {
document.body.removeEventListener('click', onBody)
}
}, [name])上述 effect 在組件初始渲染會(huì)執(zhí)行,當(dāng) name 發(fā)生變化導(dǎo)致組件重新渲染也會(huì)執(zhí)行,相應(yīng)的,組件卸載時(shí)和由 name 變化導(dǎo)致組件渲染之后將清理上一個(gè) effect。
提示:函數(shù)組件每次渲染時(shí),effect 都是一個(gè)不同的函數(shù),在函數(shù)組件內(nèi)的每一個(gè)位置(包括事件處理函數(shù),effects,定時(shí)器等等)只能拿到定義它們的那次渲染的 props 和 state。
函數(shù)組件生命周期內(nèi) hooks 的調(diào)用順序如下圖

以上就是React中useState和useEffect的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于React useState useEffect的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)導(dǎo)航欄二級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
react中hooks使用useState的更新不觸發(fā)dom更新問題及解決
這篇文章主要介紹了react中hooks使用useState的更新不觸發(fā)dom更新問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
react-router v4如何使用history控制路由跳轉(zhuǎn)詳解
這篇文章主要給大家介紹了關(guān)于react-router v4如何使用history控制路由跳轉(zhuǎn)的相關(guān)資料,文中通過示例代碼介紹的的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01
React?SSR架構(gòu)Stream?Rendering與Suspense?for?Data?Fetching
這篇文章主要為大家介紹了React?SSR架構(gòu)Stream?Rendering與Suspense?for?Data?Fetching解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
React實(shí)現(xiàn)點(diǎn)擊切換組件效果
這篇文章主要為大家詳細(xì)介紹了如何基于React實(shí)現(xiàn)點(diǎn)擊切換組件效果,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以學(xué)習(xí)一下2023-08-08
react系列從零開始_簡(jiǎn)單談?wù)剅eact
下面小編就為大家?guī)硪黄猺eact系列從零開始_簡(jiǎn)單談?wù)剅eact。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07
react后臺(tái)系統(tǒng)最佳實(shí)踐示例詳解
這篇文章主要為大家介紹了react后臺(tái)系統(tǒng)最佳實(shí)踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01

