React中useState和useEffect的用法詳解
之前在不討論 React Hooks 和組件生命周期的基礎(chǔ)上介紹了函數(shù)組件和類(lèi)組件的差別,現(xiàn)在介紹一個(gè)為函數(shù)組件而生的知識(shí)點(diǎn),即:React Hooks。Hooks 是函數(shù),在 React 16.8 正式發(fā)布,它對(duì)類(lèi)組件沒(méi)有影響。
類(lèi)組件的功能強(qiáng)大,能擁有自己的state,有生命周期,開(kāi)發(fā)人員能根據(jù)需求在特定的生命周期中執(zhí)行自己要想的操作,如:發(fā)送Ajax請(qǐng)求等。類(lèi)組件功能雖強(qiáng),但它存在如下問(wèn)題:
1.必須時(shí)常關(guān)注 this 關(guān)鍵字的指向,這對(duì)初學(xué)者而言不是一件容易的事。
2.相同的生命周期函數(shù)在類(lèi)組件中最多定義一個(gè),這導(dǎo)致彼此無(wú)關(guān)的邏輯代碼被揉雜在同一生命周期函數(shù)中。
3.不同的生命周期函數(shù)可能包含相同的代碼,最常見(jiàn)便是 componentDidMount 和 componentDidUpdate。
Hooks 發(fā)布之后,函數(shù)組件能擁有自己的 state,也能感知到組件被銷(xiāo)毀和 DOM 被繪制到屏幕上的時(shí)機(jī),但是沒(méi)有類(lèi)組件存在的那三個(gè)問(wèn)題。React提供了很多內(nèi)置的Hooks,在本文介紹 useState 和 useEffect。
useState
它是一個(gè)與狀態(tài)管理相關(guān)的hook,它讓函數(shù)組件擁有狀態(tài),是最常用的Hooks之一,類(lèi)型定義如下:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]; function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
從類(lèi)型定義可以看出,useState 有兩個(gè)重載,分別是傳參數(shù)和不傳參數(shù),不論是否傳參數(shù),useState 都返回一個(gè)長(zhǎng)度為 2 的數(shù)組,數(shù)組的第一個(gè)位置是狀態(tài),它可以是任何數(shù)據(jù)類(lèi)型,類(lèi)型參數(shù) S 用于注釋它的類(lèi)型,第二個(gè)位置是一個(gè)用于更新?tīng)顟B(tài)的函數(shù),為了語(yǔ)言上的便利本小節(jié)將該函數(shù)記為 setState。接下來(lái),介紹 useState 的基本用法。
當(dāng) useState 的參數(shù)不是函數(shù)
此時(shí),useState 的參數(shù)作為狀態(tài)的初始值,如果沒(mé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 類(lèi)型,初始值為'何遇',age 的數(shù)據(jù)類(lèi)型是 number 或 undefined,初始值為undefined。
當(dāng) useState 的參數(shù)是函數(shù)
此時(shí),函數(shù)的返回值是狀態(tài)的初始值。某些時(shí)候,狀態(tài)的初始值要經(jīng)過(guò)計(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ù)類(lèi)型是 number。
修改狀態(tài)的值
延用上述代碼中的 setCount,修改狀態(tài)有兩種方式。用法如下:
// 用法一 setCount((count) => { return count + 1 }) // 用法二 setCount(0)
如果 setCount 的參數(shù)是函數(shù),那么 count 現(xiàn)在的值將以參數(shù)的形式傳給該函數(shù),函數(shù)的返回值用于更新?tīng)顟B(tài)。如果 setCount 的參數(shù)不是函數(shù),那么該參數(shù)將用于更新?tīng)顟B(tài)。修改狀態(tài)只能使用與它相關(guān)的 setState ,且必須滿(mǎn)足 Immutability 原則,狀態(tài)值發(fā)生變更將導(dǎo)致組件重新渲染,重新渲染時(shí),useState 返回的第一個(gè)值始終是狀態(tài)最新的值,不會(huì)重置為初始值。
目前已介紹完 useState 的基本用法,觀(guān)察下面這段更復(fù)雜的代碼,分析瀏覽器打印的結(jié)果。
function UseStateAdvanceDemo() { // count 的初始值為 0 const [count, setCount] = useState<number>(0) const onClick = () => { setCount((prevCount) => prevCount + 1) // 將count在原來(lái)的基礎(chǔ)上加1 setTimeout(() => { console.log(count) // 分析瀏覽器打印的結(jié)果 }, 1000) } return <button onClick={onClick}>打開(kāi)開(kāi)發(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 能讓開(kāi)發(fā)人員知道 DOM 什么時(shí)候被繪制到了屏幕,組件什么時(shí)候被銷(xiāo)毀了,有些開(kāi)發(fā)人員認(rèn)為它是類(lèi)組件 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期函數(shù)的結(jié)合,但實(shí)際上函數(shù)組件沒(méi)有與類(lèi)組件類(lèi)似的生命周期概念。
useEffect 類(lèi)型定義如下:
type EffectCallback = () => (void | Destructor); function useEffect(effect: EffectCallback, deps?: DependencyList): void;
從類(lèi)型定義可以看出,useEffect 最多接受兩個(gè)參數(shù),第一個(gè)參數(shù)是函數(shù),可以有返回值,之后將該函數(shù)稱(chēng)為 effect;第二個(gè)參數(shù)非必填,是個(gè)數(shù)組,它是 effect 的依賴(lài),稱(chēng)為 deps。deps 用于確定 effect 在本次渲染是否執(zhí)行,如果執(zhí)行,那么在瀏覽器將 DOM 繪制到屏幕之后執(zhí)行,可以將 Ajax 請(qǐng)求,訪(fǎng)問(wèn) DOM 等操作放在effect 中,它不會(huì)阻塞瀏覽器繪制。函數(shù)組件可以多次使用 useEffect,每使用一次就定義一個(gè) effect,這些 effect 的執(zhí)行順序與它們被定義的順序一致,建議將不同職責(zé)的代碼放在不同的 effect 中。接下來(lái)從 effect 的清理工作和它的依賴(lài)這兩個(gè)方面介紹 useEffect。
effect沒(méi)有清理工作
effect 沒(méi)有清理工作就意味著它沒(méi)有返回值,代碼如下:
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 的依賴(lài)
前面兩個(gè)示例定義的 effect 沒(méi)有指明依賴(lài),因此組件每一輪渲染都會(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 沒(méi)有依賴(lài),該 effect 只在組件初始渲染時(shí)執(zhí)行,它的清理工作在組件卸載時(shí)執(zhí)行。對(duì)于綁定 DOM 事件而言這是一件好事,它可以防止事件反復(fù)綁定和解綁,但問(wèn)題是,如果在事件處理程序中訪(fǎng)問(wèn)組件的 state 和 props,那么只能拿到它們的初始值,拿不到最新的值。是否有辦法讓 effect 始終拿到 state 和 props 最新的值呢?有。
給 effect 傳遞依賴(lài)項(xiàng),React 會(huì)將本次渲染時(shí)依賴(lài)項(xiàng)的值與上一次渲染時(shí)的值進(jìn)行淺對(duì)比,如果結(jié)論是它們其中之一有變化,那么該 effect 會(huì)被執(zhí)行,否則不會(huì)執(zhí)行。為了讓 effect 拿到它所需 state 和 props 的最新值,effect 中所有要訪(fǎng)問(wèn)的外部變量都應(yīng)該作為依賴(lài)項(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路由的history對(duì)象的插件history的使用解讀
這篇文章主要介紹了React路由的history對(duì)象的插件history的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10如何將你的AngularJS1.x應(yīng)用遷移至React的方法
本篇文章主要介紹了如何將你的AngularJS1.x應(yīng)用遷移至React的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02react使用axios進(jìn)行api網(wǎng)絡(luò)請(qǐng)求的封裝方法詳解
這篇文章主要為大家詳細(xì)介紹了react使用axios進(jìn)行api網(wǎng)絡(luò)請(qǐng)求的封裝方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03React通過(guò)useContext特性實(shí)現(xiàn)組件數(shù)據(jù)傳遞
本文主要介紹了React如何通過(guò)useContext特性實(shí)現(xiàn)組件數(shù)據(jù)傳遞,文中有相關(guān)的代碼示例供大家參考,對(duì)我們學(xué)習(xí)React有一定的幫助,需要的朋友可以參考下2023-06-06react-redux的connect與React.forwardRef結(jié)合ref失效的解決
這篇文章主要介紹了react-redux的connect與React.forwardRef結(jié)合ref失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05再次談?wù)揜eact.js實(shí)現(xiàn)原生js拖拽效果引起的一系列問(wèn)題
React 起源于 Facebook 的內(nèi)部項(xiàng)目,因?yàn)樵摴緦?duì)市場(chǎng)上所有 JavaScript MVC 框架,都不滿(mǎn)意,就決定自己寫(xiě)一套,用來(lái)架設(shè) Instagram 的網(wǎng)站.本文給大家介紹React.js實(shí)現(xiàn)原生js拖拽效果,需要的朋友一起學(xué)習(xí)吧2016-04-04關(guān)于react+antd樣式不生效問(wèn)題的解決方式
最近本人在使用Antd開(kāi)發(fā)時(shí)遇到些問(wèn)題,所以下面這篇文章主要給大家介紹了關(guān)于react+antd樣式不生效問(wèn)題的解決方式,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07React?+?Typescript領(lǐng)域初學(xué)者的常見(jiàn)問(wèn)題和技巧(最新)
這篇文章主要介紹了React?+?Typescript領(lǐng)域初學(xué)者的常見(jiàn)問(wèn)題和技巧,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06