useEffect的閉包陷阱及useInterval

首先先看一段代碼:

import { useEffect, useState } from 'react';

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

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

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

    return <div>count: {count}</div>;
}

export default App;

結(jié)果是:頁面上count一直顯示1讹俊;
解析:useEffect的第二個參數(shù)為空數(shù)組牵啦,所以只會在組件加載后僅執(zhí)行一次迫皱,我們知道組件每次render的時候都會生成一個新的state對象茎活,對應(yīng)一個快照昙沦,上述代碼中,因為useEffect只執(zhí)行了一次载荔,所以定時器中的count 一直是最初快照里的count桅滋,那么頁面中count的顯示肯定不會改變;

閉包陷阱產(chǎn)生的原因就是 useEffect 的函數(shù)里引用了某個 state身辨,形成了閉包(也有叫過時的閉包)

那么我們怎么樣才能每次都拿到最新的count呢丐谋?
解決一:使用useEffect的第二個參數(shù),count變化時煌珊,重新執(zhí)行setInterval号俐,并且在useEffect的清理函數(shù)中執(zhí)行clearInterval,這樣我們就可以在頁面上看到變化的count了6ㄢ帧吏饿!

import { useEffect, useState } from 'react';

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

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

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

    return <div>count: {count}</div>;
}

export default App;

但是!J哒恪猪落!這種方法有一定的缺點,因為每次count變了都要重置定制器畴博,這樣可能會導(dǎo)致計時不準(zhǔn)確笨忌;
所以,這種把依賴的 state 添加到 deps 里的方式是能解決閉包陷阱俱病,但是定時器不能這樣做官疲;
我們采用useRef的方式8そ帷!途凫!

解法二:最主要的是setCount(count => count +1)垢夹,使用函數(shù)作為參數(shù),接受一個舊的state维费,得到新的state果元;
使用useRef來保存回調(diào)函數(shù),在useEffect中從 ref.current 來取函數(shù)再調(diào)用犀盟,在useLayoutEffect中給ref賦值新的fn而晒,這個fn里的state是最新的;

import { useEffect, useLayoutEffect, useRef } from 'react';

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

    const fn = () => {
        //還可以做一些其他邏輯操作
        console.log(count);
    };
    
    const ref = useRef(()=>{});

   useEffect(() => {
        setInterval(() => {
            //最關(guān)鍵的一步且蓬,使用函數(shù)欣硼,接受一個舊的state题翰,得到新的state
            //所以就會render
            setCount(count => count + 1);
        }, 1000);
    }, []);
    
    //每次在render前都給ref賦值新的fn恶阴,這個fn里的state是最新值
    useLayoutEffect(() => {
        ref.current = fn;
    });

    useEffect(() => {
        setInterval(() => ref.current(), 1000);
    }, []);

    return <div>count: {count}</div>;
}

export default App;

以上這個代碼可以封裝成useInterval

//useInterval
import { useEffect, useLayoutEffect, useRef } from 'react';

const useInterval = (fn: Function, delay: number)=>{
    const ref = useRef<Function>(()=>{})

    useLayoutEffect(()=>{
        ref.current = fn
    })

    useEffect(()=>{
        setInterval(()=>{
            ref.current()
        }, delay)
    }, [])
}

export default useInterval
import useInterval from './useInterval';

const App = () => {
    const [count,setCount] = useState(0);
    useInterval(()=>{
        setCount(count => count+1)
    }, 1000)
    useInterval(()=>{
        console.log(count, 'count')
    }, 1000)
    
    return <div>count: {count}</div>;
}

export default App;

擴展知識

  • 使用useEffect時,若有多個副作用豹障,則應(yīng)該調(diào)用多個useEffect冯事,而不是寫在一個里面;
  • useEffect第一個參數(shù)可以返回一個函數(shù)血公,這個函數(shù)會在組件卸載時(也就是render了昵仅,生成新的快照時)執(zhí)行,可以用來清除副作用里的操作累魔;
  • useLayoutEffect是在render前同步執(zhí)行的(和componentDidMount等價)摔笤,useEffect是在render后異步執(zhí)行的;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垦写,一起剝皮案震驚了整個濱河市吕世,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梯投,老刑警劉巖命辖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異分蓖,居然都是意外死亡尔艇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門么鹤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來终娃,“玉大人,你說我怎么就攤上這事蒸甜〕⒍叮” “怎么了毡们?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昧辽。 經(jīng)常有香客問我衙熔,道長,這世上最難降的妖魔是什么搅荞? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任红氯,我火速辦了婚禮,結(jié)果婚禮上咕痛,老公的妹妹穿的比我還像新娘痢甘。我一直安慰自己,他們只是感情好茉贡,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布塞栅。 她就那樣靜靜地躺著,像睡著了一般腔丧。 火紅的嫁衣襯著肌膚如雪放椰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天愉粤,我揣著相機與錄音砾医,去河邊找鬼。 笑死衣厘,一個胖子當(dāng)著我的面吹牛如蚜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播影暴,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼错邦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了型宙?” 一聲冷哼從身側(cè)響起撬呢,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎早歇,沒想到半個月后倾芝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡箭跳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年晨另,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谱姓。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡借尿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情路翻,我是刑警寧澤狈癞,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站茂契,受9級特大地震影響蝶桶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掉冶,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一真竖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厌小,春花似錦恢共、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至癣蟋,卻和暖如春透硝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梢薪。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工蹬铺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尝哆,地道東北人秉撇。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像秋泄,于是被迫代替她去往敵國和親琐馆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容