1. React官方團(tuán)隊(duì)出手,補(bǔ)齊原生Hook短板

        共 2508字,需瀏覽 6分鐘

         ·

        2022-05-11 12:12

        我們知道,Hooks使用時(shí)存在所謂的「閉包陷阱」,考慮如下代碼:

        function?Chat()?{
        ??const?[text,?setText]?=?useState('');

        ??const?onClick?=?useCallback(()?=>?{
        ????sendMessage(text);
        ??},?[]);

        ??return?<SendButton?onClick={onClick}?/>;
        }

        我們期望點(diǎn)擊后sendMessage能傳遞text的最新值。

        然而實(shí)際上,由于回調(diào)函數(shù)被useCallback緩存,形成閉包,所以點(diǎn)擊的效果始終是sendMessage('')

        這就是「閉包陷阱」。

        以上代碼的一種解決方式是「為useCallback增加依賴項(xiàng)」

        const?onClick?=?useCallback(()?=>?{
        ??sendMessage(text);
        },?[text]);

        但是這么做了后,每當(dāng)依賴項(xiàng)(text)變化,useCallback會(huì)返回一個(gè)全新的onClick引用,這就失去了useCallback「緩存函數(shù)引用」的作用。

        「閉包陷阱」的出現(xiàn),加大了Hooks的上手門檻,也讓開(kāi)發(fā)者更容易寫出有bug的代碼。

        現(xiàn)在,React官方團(tuán)隊(duì)要出手解決這個(gè)問(wèn)題。

        useEvent

        解決方式是引入一個(gè)新的原生Hook —— useEvent。

        他用于定義一個(gè)函數(shù),這個(gè)函數(shù)有2個(gè)特性:

        1. 在組件多次render時(shí)保持引用一致

        2. 函數(shù)內(nèi)始終能獲取到最新的propsstate

        上面的例子使用useEvent改造后:

        function?Chat()?{
        ??const?[text,?setText]?=?useState('');

        ??const?onClick?=?useEvent(()?=>?{
        ????sendMessage(text);
        ??});

        ??return?<SendButton?onClick={onClick}?/>;
        }

        Chat組件多次render時(shí),onClick始終指向同一個(gè)引用。

        并且onClick觸發(fā)時(shí)始終能獲取到text的最新值。

        之所以叫useEvent,是因?yàn)?code style="font-size:14px;font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;color:rgb(145,109,213);font-weight:bolder;">React團(tuán)隊(duì)認(rèn)為這個(gè)Hook的主要應(yīng)用場(chǎng)景是:「封裝事件處理函數(shù)」。

        useEvent的實(shí)現(xiàn)

        useEvent的實(shí)現(xiàn)并不困難,代碼類似如下:

        function?useEvent(handler)?{
        ??const?handlerRef?=?useRef(null);

        ??//?視圖渲染完成后更新`handlerRef.current`指向
        ??useLayoutEffect(()?=>?{
        ????handlerRef.current?=?handler;
        ??});

        ??//?用useCallback包裹,使得render時(shí)返回的函數(shù)引用一致
        ??return?useCallback((...args)?=>?{
        ????const?fn?=?handlerRef.current;
        ????return?fn(...args);
        ??},?[]);
        }

        整體包括兩部分:

        1. 返回一個(gè)沒(méi)有依賴項(xiàng)的useCallback,使得每次render時(shí)函數(shù)的引用一致
        useCallback((...args)?=>?{
        ??const?fn?=?handlerRef.current;
        ??return?fn(...args);
        },?[]);
        1. 在合適的時(shí)機(jī)更新handlerRef.current,使得實(shí)際執(zhí)行的函數(shù)始終是最新的引用

        與開(kāi)源Hooks的差異

        很多開(kāi)源Hooks庫(kù)已經(jīng)實(shí)現(xiàn)類似功能(比如ahooks中的useMemoizedFn

        useEvent與這些開(kāi)源實(shí)現(xiàn)的差異主要體現(xiàn)在:

        useEvent定位于「處理事件回調(diào)函數(shù)」這一單一場(chǎng)景,而useMemoizedFn定位于「緩存各種函數(shù)」。

        那么問(wèn)題來(lái)了,既然功能類似,那useEvent為什么要限制自己的使用場(chǎng)景呢?

        答案是:為了更穩(wěn)定。

        useEvent能否獲取到最新的stateprops取決于handlerRef.current更新的時(shí)機(jī)。

        在上面模擬實(shí)現(xiàn)中,useEvent更新handlerRef.current的邏輯放在useLayoutEffect回調(diào)中進(jìn)行。

        這就保證了handlerRef.current始終在「視圖完成渲染」后再更新:

        useLayoutEffect(()?=>?{
        ??handlerRef.current?=?handler;
        });

        「事件回調(diào)」觸發(fā)的時(shí)機(jī)顯然在「視圖完成渲染」之后,所以能夠穩(wěn)定獲取到最新的stateprops。

        注:源碼內(nèi)的實(shí)際更新時(shí)機(jī)會(huì)更早些,但不影響這里的結(jié)論

        再來(lái)看看ahooks中的useMemoizedFn,fnRef.current的更新時(shí)機(jī)是「useMemoizedFn執(zhí)行時(shí)」(即「組件render時(shí)」):

        function?useMemoizedFn<T?extends?noop>(fn:?T)?{
        ??const?fnRef?=?useRef(fn);

        ??//?更新fnRef.current
        ??fnRef.current?=?useMemo(()?=>?fn,?[fn]);

        ??//?...省略代碼
        }

        當(dāng)React18啟用「并發(fā)更新」后,組件render的次數(shù)、時(shí)機(jī)并不確定。

        所以useMemoizedFnfnRef.current的更新時(shí)機(jī)也是不確定的。

        這就增加了在「并發(fā)更新」下使用時(shí)潛在的風(fēng)險(xiǎn)。

        可以說(shuō),useEvent通過(guò)限制handlerRef.current更新時(shí)機(jī),進(jìn)而限制應(yīng)用場(chǎng)景,最終達(dá)到穩(wěn)定的目的。

        總結(jié)

        useEvent當(dāng)前還處于RFC(Request For Comments)[1]階段。

        很多熱心的開(kāi)發(fā)者對(duì)這個(gè)Hook的命名提出了建議,比如:useStableCallback

        804590fd682f4172e6ffdce6e96df7e9.webp

        又比如:useLatestClosure

        af4c5c327c127bed4d8abaf0b3dbb737.webp

        從這些命名看,他們顯然擴(kuò)大了useEvent的應(yīng)用場(chǎng)景。

        經(jīng)過(guò)本文的分析我們知道,「擴(kuò)大應(yīng)用場(chǎng)景」意味著「增加開(kāi)發(fā)者使用時(shí)出錯(cuò)的風(fēng)險(xiǎn)」。

        參考資料

        [1]

        RFC(Request For Comments): https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md

        瀏覽 61
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 大奶伊人 | 和尚你好大轻点h | 成人影院无码 | 欧美日韩大逼逼 | 综合毛片 |