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

以上就是React中useState和useEffect的用法詳解的詳細內容,更多關于React useState useEffect的資料請關注腳本之家其它相關文章!
相關文章
React路由的history對象的插件history的使用解讀
這篇文章主要介紹了React路由的history對象的插件history的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
如何將你的AngularJS1.x應用遷移至React的方法
本篇文章主要介紹了如何將你的AngularJS1.x應用遷移至React的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
react-redux的connect與React.forwardRef結合ref失效的解決
這篇文章主要介紹了react-redux的connect與React.forwardRef結合ref失效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
再次談論React.js實現原生js拖拽效果引起的一系列問題
React 起源于 Facebook 的內部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram 的網站.本文給大家介紹React.js實現原生js拖拽效果,需要的朋友一起學習吧2016-04-04
React?+?Typescript領域初學者的常見問題和技巧(最新)
這篇文章主要介紹了React?+?Typescript領域初學者的常見問題和技巧,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06

