自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從根上理解 React Hooks 的閉包陷阱

開(kāi)發(fā) 架構(gòu)
相信很多用過(guò) hooks 的人都遇到過(guò)這個(gè)坑,今天我們來(lái)思考下 hooks 閉包陷阱的原因和怎么解決。

現(xiàn)在開(kāi)發(fā) React 組件基本都是用 hooks 了,hooks 很方便,但一不注意也會(huì)遇到閉包陷阱的坑。

相信很多用過(guò) hooks 的人都遇到過(guò)這個(gè)坑,今天我們來(lái)思考下 hooks 閉包陷阱的原因和怎么解決。

首先這樣一段代碼,大家覺(jué)得有問(wèn)題沒(méi):

import { useEffect, useState } from 'react';

function Dong() {

const [count,setCount] = useState(0);

useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 500);
}, []);

useEffect(() => {
setInterval(() => {
console.log(count);
}, 500);
}, []);

return <div>guang</div>;
}

export default Dong;

用 useState 創(chuàng)建了個(gè) count 狀態(tài),在一個(gè) useEffect 里定時(shí)修改它,另一個(gè) useEffect 里定時(shí)打印最新的 count 值。

我們跑一下:

打印的并不是我們預(yù)期的 0、1、2、3,而是 0、0、0、0,這是為什么呢?

這就是所謂的閉包陷阱。

首先,我們回顧下 hooks 的原理:hooks 就是在 fiber 節(jié)點(diǎn)上存放了 memorizedState 鏈表,每個(gè) hook 都從對(duì)應(yīng)的鏈表元素上存取自己的值。

比如上面 useState、useEffect、useEffect 的 3 個(gè) hook 就對(duì)應(yīng)了鏈表中的 3 個(gè) memorizedState:

然后 hook 是存取各自的那個(gè) memorizedState 來(lái)完成自己的邏輯。

hook 鏈表有創(chuàng)建和更新兩個(gè)階段,也就是 mount 和 update,第一次走 mount 創(chuàng)建鏈表,后面都走 update。

比如 useEffect 的實(shí)現(xiàn):

特別要注意 deps 參數(shù)的處理,如果 deps 為 undefined 就被當(dāng)作 null 來(lái)處理了。

那之后又怎么處理的呢?

會(huì)取出新傳入的 deps 和之前存在 memorizedState 的 deps 做對(duì)比,如果沒(méi)有變,就直接用之前傳入的那個(gè)函數(shù),否則才會(huì)用新的函數(shù)。

deps 對(duì)比的邏輯很容易看懂,如果是之前的 deps 是 null,那就返回 false 也就是不相等,否則遍歷數(shù)組依次對(duì)比:

所以:

如果 useEffect 第二個(gè)參數(shù)傳入 undefined 或者 null,那每次都會(huì)執(zhí)行。

如果傳入了一個(gè)空數(shù)組,只會(huì)執(zhí)行一次。

否則會(huì)對(duì)比數(shù)組中的每個(gè)元素有沒(méi)有改變,來(lái)決定是否執(zhí)行。

這些我們應(yīng)該比較熟了,但是現(xiàn)在從源碼理清了。

同樣,useMemo、useCallback 等也是同樣的 deps 處理:

理清了 useEffect 等 hook 是在哪里存取數(shù)據(jù)的,怎么判斷是否執(zhí)行傳入的函數(shù)的之后,再回來(lái)看下那個(gè)閉包陷阱問(wèn)題。

我們是這樣寫(xiě)的:

useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 500);
}, []);

useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 500);
}, []);

deps 傳入了空數(shù)組,所以只會(huì)執(zhí)行一次。

對(duì)應(yīng)的源碼實(shí)現(xiàn)是這樣的:

如果是需要執(zhí)行的 effect 會(huì)打上 HasEffect 的標(biāo)記,然后后面會(huì)執(zhí)行:

因?yàn)?deps 數(shù)組是空數(shù)組,所以沒(méi)有 HasEffect 的標(biāo)記,就不會(huì)再執(zhí)行。

我們知道了為什么只執(zhí)行一次,那只執(zhí)行一次有什么問(wèn)題呢?定時(shí)器確實(shí)只需要設(shè)置一次呀?

定時(shí)器確實(shí)只需要設(shè)置一次沒(méi)錯(cuò),但是在定時(shí)器里用到了會(huì)變化的 state,這就有問(wèn)題了:

deps 設(shè)置了空數(shù)組,那多次 render,只有第一次會(huì)執(zhí)行傳入的函數(shù):

但是 state 是變化的呀,執(zhí)行的那個(gè)函數(shù)卻一直引用著最開(kāi)始的 state。

怎么解決這個(gè)問(wèn)題呢?

每次 state 變了重新創(chuàng)建定時(shí)器,用新的 state 變量不就行了:

也就是這樣的:

import { useEffect, useState } from 'react';

function Dong() {

const [count,setCount] = useState(0);

useEffect(() => {
setInterval(() => {
setCount(count + 1);
}, 500);
}, [count]);

useEffect(() => {
setInterval(() => {
console.log(count);
}, 500);
}, [count]);

return <div>guang</div>;
}

export default Dong;

這樣每次 count 變了就會(huì)執(zhí)行引用了最新 count 的函數(shù)了:

現(xiàn)在確實(shí)不是全 0 了,但是這亂七八遭的打印是怎么回事?

那是因?yàn)楝F(xiàn)在確實(shí)是執(zhí)行傳入的 fn 來(lái)設(shè)置新定時(shí)器了,但是之前的那個(gè)沒(méi)有清楚呀,需要加入一段清除邏輯:

import { useEffect, useState } from 'react';

function Dong() {

const [count,setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 500);
return () => clearInterval(timer);
}, [count]);

useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 500);
return () => clearInterval(timer);
}, [count]);

return <div>guang</div>;
}

export default Dong;

加上了 clearInterval,每次執(zhí)行新的函數(shù)之前會(huì)把上次設(shè)置的定時(shí)器清掉。

再試一下:

現(xiàn)在就是符合我們預(yù)期的了,打印 0、1、2、3、4。

很多同學(xué)學(xué)了 useEffect 卻不知道要返回一個(gè)清理函數(shù),現(xiàn)在知道為啥了吧。就是為了再次執(zhí)行的時(shí)候清掉上次設(shè)置的定時(shí)器、事件監(jiān)聽(tīng)器等的。

這樣我們就完美解決了 hook 閉包陷阱的問(wèn)題。

總結(jié)

hooks 雖然方便,但是也存在閉包陷阱的問(wèn)題。

我們過(guò)了一下 hooks 的實(shí)現(xiàn)原理:

在 fiber 節(jié)點(diǎn)的 memorizedState 屬性存放一個(gè)鏈表,鏈表節(jié)點(diǎn)和 hook 一一對(duì)應(yīng),每個(gè) hook 都在各自對(duì)應(yīng)的節(jié)點(diǎn)上存取數(shù)據(jù)。

useEffect、useMomo、useCallback 等都有 deps 的參數(shù),實(shí)現(xiàn)的時(shí)候會(huì)對(duì)比新舊兩次的 deps,如果變了才會(huì)重新執(zhí)行傳入的函數(shù)。所以 undefined、null 每次都會(huì)執(zhí)行,[] 只會(huì)執(zhí)行一次,[state] 在 state 變了才會(huì)再次執(zhí)行。

閉包陷阱產(chǎn)生的原因就是 useEffect 等 hook 里用到了某個(gè) state,但是沒(méi)有加到 deps 數(shù)組里,這樣導(dǎo)致 state 變了卻沒(méi)有執(zhí)行新傳入的函數(shù),依然引用的之前的 state。

閉包陷阱的解決也很簡(jiǎn)單,正確設(shè)置 deps 數(shù)組就可以了,這樣每次用到的 state 變了就會(huì)執(zhí)行新函數(shù),引用新的 state。不過(guò)還要注意要清理下上次的定時(shí)器、事件監(jiān)聽(tīng)器等。

要理清 hooks 閉包陷阱的原因是要理解 hook 的原理的,什么時(shí)候會(huì)執(zhí)行新傳入的函數(shù),什么時(shí)候不會(huì)。

hooks 的原理確實(shí)也不難,就是在 memorizedState 鏈表上的各節(jié)點(diǎn)存取數(shù)據(jù),完成各自的邏輯的,唯一需要注意的是 deps 數(shù)組引發(fā)的這個(gè)閉包陷阱問(wèn)題。

責(zé)任編輯:武曉燕 來(lái)源: 神光的編程秘籍
相關(guān)推薦

2022-05-05 08:31:48

useRefuseEffecthook

2024-01-08 08:35:28

閉包陷阱ReactHooks

2021-02-24 07:40:38

React Hooks閉包

2021-05-11 08:48:23

React Hooks前端

2022-08-21 09:41:42

ReactVue3前端

2016-10-27 19:26:47

Javascript閉包

2016-09-18 20:53:16

JavaScript閉包前端

2022-10-24 08:08:27

閉包編譯器

2011-03-02 12:33:00

JavaScript

2019-08-20 15:16:26

Reacthooks前端

2022-06-08 08:01:20

useEffect數(shù)組函數(shù)

2017-05-22 16:08:30

前端開(kāi)發(fā)javascript閉包

2023-11-06 08:00:00

ReactJavaScript開(kāi)發(fā)

2020-07-29 10:10:37

HTTP緩存前端

2020-04-27 09:40:13

Reacthooks前端

2020-10-28 09:12:48

React架構(gòu)Hooks

2022-05-06 16:18:00

Block和 C++OC 類lambda

2010-07-26 11:27:58

Perl閉包

2021-03-18 08:00:55

組件Hooks React

2022-03-31 17:54:29

ReactHooks前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)