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

從根上理解 React Hooks 的閉包陷阱(續(xù)集)

開發(fā) 前端
useRef 能解決閉包陷阱的原因是 useEffect 等 hook 里不直接引用 state,而是引用 ref.current,這樣后面只要修改了 ref 中的值,這里取出來的就是最新的。

??上篇文章??我們知道了什么是 hooks 的閉包陷阱,它的產(chǎn)生原因和解決方式,并通過一個案例做了演示。

其實(shí)那個案例的閉包陷阱的解決方式不夠完善,這篇文章我們再完善一下。

首先我們先來回顧下什么是閉包陷阱:

hooks 的閉包陷阱是指 useEffect 等 hook 中用到了某個 state,但是沒有把它加到 deps 數(shù)組里,導(dǎo)致 state 變了,但是執(zhí)行的函數(shù)依然引用著之前的 state。

它的解決方式就是正確設(shè)置 deps 數(shù)組,把用到的 state 放到 deps 數(shù)組里,這樣每次 state 變了就能執(zhí)行最新的函數(shù),引用新的 state。同時要清理上次的定時器、事件監(jiān)聽器等。

我們舉了這樣一個例子:

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;

每次打印都是 0 :

解決方式就是把 count 設(shè)置到 deps 里,并添加清理函數(shù):

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;

這樣就能解決閉包陷阱:

但是這種解決閉包陷阱的方式用在定時器上不是很合適。

為什么呢?

因?yàn)楝F(xiàn)在每次 count 變了就會重置定時器,那之前的計(jì)時就重新計(jì)算,這樣就會導(dǎo)致計(jì)時不準(zhǔn)。

所以,這種把依賴的 state 添加到 deps 里的方式是能解決閉包陷阱,但是定時器不能這樣做。

那還有什么方式能解決閉包陷阱呢?

useRef。

閉包陷阱產(chǎn)生的原因就是 useEffect 的函數(shù)里引用了某個 state,形成了閉包,那不直接引用不就行了?

useRef 是在 memorizedState 鏈表中放一個對象,current 保存某個值。

它的源碼是這樣的:

初始化的時候創(chuàng)建了一個對象放在 memorizedState 上,后面始終返回這個對象。

這樣通過 useRef 保存回調(diào)函數(shù),然后在 useEffect 里從 ref.current 來取函數(shù)再調(diào)用,避免了直接調(diào)用,也就沒有閉包陷阱的問題了。

也就是這樣:

const fn = () => {
console.log(count);
};
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), 500);
}, []);

useEffect 里執(zhí)行定時器,deps 設(shè)置為了 [],所以只會執(zhí)行一次,回調(diào)函數(shù)用的是 ref.current,沒有直接依賴某個 state,所以不會有閉包陷阱。

用 useRef 創(chuàng)建個 ref 對象,初始值為打印 count 的回調(diào)函數(shù),每次 render 都修改下其中的函數(shù)為新創(chuàng)建的函數(shù),這個函數(shù)里引用的 count 就是最新的。

這里用了 useLayoutEffect 而不是 useEffect 是因?yàn)?useLayoutEffect 是在 render 前同步執(zhí)行的,useEffect 是在 render 后異步執(zhí)行的,所以用 useLayoutEffect 能保證在 useEffect 之前被調(diào)用。

這種方式避免了 useEffect 里直接對 state 的引用,從而避免了閉包問題。

另外,修改 count 的地方,可以用 setCount(count => count + 1) 代替 setCount(count + 1),這樣也就避免了閉包問題:

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

現(xiàn)在組件的代碼是這樣的:

import { useEffect, useLayoutEffect, useState, useRef } from 'react';
function Dong() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
setCount(count => count + 1);
}, 500);
}, []);
const fn = () => {
console.log(count);
};
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), 500);
}, []);
return <div>guang</div>;
}
export default Dong;

測試下:

確實(shí),打印也是正常的,這就是解決閉包陷阱的第二種方式,通過 useRef 避免直接對 state 的引用,從而避免閉包問題。

這段邏輯用到了多個 hook,可以封裝成個自定義 hook:

function useInterval(fn, time) {
const ref = useRef(fn);
useLayoutEffect(() => {
ref.current = fn;
});
useEffect(() => {
setInterval(() => ref.current(), time);
}, []);
}

然后組件代碼就可以簡化了:

function Dong() {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(count + 1);
}, 500);
useInterval(() => {
console.log(count);
}, 500);
return <div>guang</div>;
}

這樣我們就用 useRef 的方式解決了閉包陷阱問題。

總結(jié)

上篇文章我們通過把依賴的 state 添加到 deps 數(shù)組中的方式,使得每次 state 變了就執(zhí)行新的函數(shù),引用新的 state,從而解決了閉包陷阱問題。

這種方式用在定時器上是不合適的,因?yàn)槎〞r器一旦被重置和重新計(jì)時,那計(jì)時就不準(zhǔn)確了。

所以我們才用了避免閉包陷阱的第二種方式:使用 useRef。

useRef 能解決閉包陷阱的原因是 useEffect 等 hook 里不直接引用 state,而是引用 ref.current,這樣后面只要修改了 ref 中的值,這里取出來的就是最新的。

然后我們把這段邏輯封裝成了個自定義 hook,這樣可以方便復(fù)用。

解決 hooks 的閉包陷阱有兩種方式:

  • 設(shè)置依賴的 state 到 deps 數(shù)組中并添加清理函數(shù)。
  • 不直接引用 state,把 state 放到 useRef 創(chuàng)建的 ref 對象中再引用。

處理定時器的時候,為保證計(jì)時的準(zhǔn)確,最好使用 useRef 的方式,其余情況兩種都可以。

責(zé)任編輯:姜華 來源: 神光的編程秘籍
相關(guān)推薦

2022-05-04 10:38:58

React閉包組件

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前端

2017-05-22 16:08:30

前端開發(fā)javascript閉包

2022-06-08 08:01:20

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

2023-11-06 08:00:00

ReactJavaScript開發(fā)

2020-07-29 10:10:37

HTTP緩存前端

2020-04-27 09:40:13

Reacthooks前端

2010-07-26 11:27:58

Perl閉包

2022-05-06 16:18:00

Block和 C++OC 類lambda

2020-10-28 09:12:48

React架構(gòu)Hooks

2022-03-31 17:54:29

ReactHooks前端

2021-03-18 08:00:55

組件Hooks React
點(diǎn)贊
收藏

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