亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解如何在React中逃離閉包陷阱

 更新時(shí)間:2023年09月25日 09:04:53   作者:QdFe  
眾所周知,JavaScript 中的閉包(Closures)一定是這種語言最可怕的特性之一,另外它可能也是最隱蔽的語言特性之一,我們在編寫 React 代碼時(shí)經(jīng)常會(huì)用到它,但是大多數(shù)時(shí)候我們甚至沒有意識(shí)到這一點(diǎn),本文小編將和大家一起深入探討如何在React中逃離閉包陷阱

眾所周知,JavaScript 中的閉包(Closures)一定是這種語言最可怕的特性之一,即使是無所不知的 ChatGPT 也是這樣說的。另外它可能也是最隱蔽的語言特性之一,我們在編寫 React 代碼時(shí)經(jīng)常會(huì)用到它,但是大多數(shù)時(shí)候我們甚至沒有意識(shí)到這一點(diǎn)。但是,我們終究還是離不開它:如果我們想編寫復(fù)雜且性能很好的 React 應(yīng)用,就必須了解閉包。所以,今天我們一起來學(xué)習(xí)以下幾點(diǎn):

  • 什么是閉包,它們是如何出現(xiàn)的,為什么我們需要它們。
  • 什么是過期的閉包,它們?yōu)槭裁磿?huì)出現(xiàn)。
  • React 中導(dǎo)致過期閉包的常見場景是什么,以及如何應(yīng)對它們。

警告:如果你從未接觸過 React 中的閉包,本文可能會(huì)讓你腦漿迸裂,在閱讀本文時(shí),請確保隨身攜帶足夠的巧克力來刺激你的腦細(xì)胞。

一個(gè)常見的問題

比如現(xiàn)在有這樣一個(gè)場景:你正在實(shí)現(xiàn)一個(gè)帶有幾個(gè)輸入字段的表單。其中一個(gè)字段是來自某個(gè)外部的組件庫。你無法訪問它的內(nèi)部結(jié)構(gòu),所以也沒辦法解決它的性能問題。但你確實(shí)需要在表單中使用它,因此你決定用 React.memo 封裝它,以便在表單中的狀態(tài)發(fā)生變化時(shí)盡量減少它的重新渲染。類似這樣:

 const HeavyComponentMemo = React.memo(HeavyComponent);
 const Form = () => {
   const [value, setValue] = useState();
   return (
     <>
       <input
         type="text"
         value={value}
         onChange={(e) => setValue(e.target.value)}
       />
       <HeavyComponentMemo />
     </>
   );
 };

這個(gè) Heavy 組件只接受一個(gè)字符串 props(比如 title)和一個(gè) onClick 回調(diào)。當(dāng)你點(diǎn)擊該組件中的 "完成" 按鈕時(shí),就會(huì)觸發(fā)這個(gè)回調(diào)。如果你想在點(diǎn)擊時(shí)提交表單數(shù)據(jù)。這也很簡單:只需將 title 和 onClick 這兩個(gè) props 傳遞給它即可。

 const HeavyComponentMemo = React.memo(HeavyComponent);
 const Form = () => {
   const [value, setValue] = useState();
   const onClick = () => {
     // submit our form data here
     console.log(value);
   };
   return (
     <>
       <input
         type="text"
         value={value}
         onChange={(e) => setValue(e.target.value)}
       />
       <HeavyComponentMemo
         title="Welcome to the form"
         onClick={onClick}
       />
     </>
   );
 };

現(xiàn)在,你又會(huì)面臨一個(gè)新的問題。我們知道,React.memo 封裝的組件上的每個(gè) props 都必須是原始值,或者在重新渲染時(shí)是保持不變的。否則,memoization 就是不起作用的。所以,從技術(shù)上講,我們需要將 onClick 包裝為 useCallback:

 const onClick = useCallback(() => {
   // submit data here
 }, []);

但我們也知道,useCallback 鉤子應(yīng)在其依賴關(guān)系數(shù)組中聲明所有依賴關(guān)系。因此,如果我們想在其中提交表單數(shù)據(jù),就必須將該數(shù)據(jù)聲明為依賴項(xiàng):

 const onClick = useCallback(() => {
   // submit data here
   console.log(value);
   // adding value to the dependency
 }, [value]);

現(xiàn)在的難題是:即使我們的 onClick 被 memo 化了,但每次表單有重新輸入時(shí),它仍然會(huì)發(fā)生變化。因此,我們的性能優(yōu)化毫無用處。

下面讓我們尋找一下其他的解決方案。React.memo 有一個(gè)叫做比較函數(shù)的東西,它允許我們對 React.memo 中的 props 比較進(jìn)行更精細(xì)的控制。通常,React 會(huì)自行比較前后的 props 。如果我們提供這個(gè)函數(shù),它將依賴于其返回的結(jié)果。如果返回結(jié)果為 true,那么 React 就會(huì)知道 props 是相同的,組件就不應(yīng)該被重新渲染,聽起來正是我們需要的。我們只需要更新一個(gè) props ,那就是我們的 title ,所以不會(huì)很復(fù)雜:

 const HeavyComponentMemo = React.memo(
   HeavyComponent,
   (before, after) => {
     return before.title === after.title;
   },
 );

這樣,完整的代碼就是這樣的:

 const HeavyComponentMemo = React.memo(
   HeavyComponent,
   (before, after) => {
     return before.title === after.title;
   },
 );
 const Form = () => {
   const [value, setValue] = useState();
   const onClick = () => {
     // submit our form data here
     console.log(value);
   };
   return (
     <>
       <input
         type="text"
         value={value}
         onChange={(e) => setValue(e.target.value)}
       />
       <HeavyComponentMemo
         title="Welcome to the form"
         onClick={onClick}
       />
     </>
   );
 };

起作用了,我們在輸入框中輸入內(nèi)容,Heavy 組件不會(huì)重新渲染,性能也不會(huì)受到影響。

但是我們又遇到了新的問題:如果在輸入框中輸入內(nèi)容,然后按下按鈕,我們在 onClick 中打印的值是 undefined 。但它不可能是 undefined,如果我在 onClick 之外添加 console.log,它就會(huì)正確打印。

 // those one logs it correctly
 console.log(value);
 const onClick = () => {
   // this is always undefined
   console.log(value);
 };

這是怎么回事呢?

這就是所謂的 "過期閉包" 問題。為了解決這個(gè)問題,我們首先需要了解一下 JavaScript 中最令人恐懼的話題:閉包及其工作原理。

JavaScript、作用域和閉包

讓我們從函數(shù)和變量開始,當(dāng)我們在 JavaScript 中聲明一個(gè)普通函數(shù)或者尖頭函數(shù)會(huì)發(fā)生什么呢?

 function something() {
   //
 }
 const something = () => {};

通過這樣的操作,我們創(chuàng)建了一個(gè)局部作用域:代碼中的一個(gè)區(qū)域,其中聲明的變量從外部是不可見的。

 const something = () => {
   const value = 'text';
 };
 console.log(value); // not going to work, "value" is local to "something" function

每次我們創(chuàng)建函數(shù)時(shí)都會(huì)發(fā)生這種情況。在另一個(gè)函數(shù)內(nèi)部創(chuàng)建的函數(shù)將具有自己的局部作用域,對于外部函數(shù)不可見。

 const something = () => {
   const inside = () => {
     const value = 'text';
   };
   console.log(value); // not going to work, "value" is local to "inside" function
 };

然而,在相反的方向就不一樣了,最里面的函數(shù)可以訪問到外部聲明的所有變量。

 const something = () => {
   const value = 'text';
   const inside = () => {
     // perfectly fine, value is available here
     console.log(value);
   };
 };

這就是通過創(chuàng)建所謂的 “閉包” 來實(shí)現(xiàn)的。內(nèi)部函數(shù) “閉包” 了來自外部的所有數(shù)據(jù),它本質(zhì)上就是所有 “外部” 數(shù)據(jù)的快照,這些數(shù)據(jù)被凍結(jié)并單獨(dú)存儲(chǔ)在內(nèi)存中。如果我們不是在 something 函數(shù)內(nèi)創(chuàng)建該值,而是將其作為參數(shù)傳遞并返回內(nèi)部函數(shù)呢:

 const something = (value) => {
   const inside = () => {
     // perfectly fine, value is available here
     console.log(value);
   };
   return inside;
 };

我們會(huì)得到這樣的行為:

 const first = something('first');
 const second = something('second');
 first(); // logs "first"
 second(); // logs "second"

我們調(diào)用 something 函數(shù)時(shí)傳入值 first,并將結(jié)果分配給一個(gè)變量。結(jié)果是對內(nèi)部聲明的函數(shù)的引用,形成閉包。從現(xiàn)在開始,只要保存這個(gè)引用的第一個(gè)變量是存在的,我們傳遞給它的值 “first” 就會(huì)被凍結(jié)掉,并且內(nèi)部函數(shù)將可以訪問它。

第二次調(diào)用也是同樣的情況:我們傳遞了一個(gè)不同的值,形成一個(gè)閉包,返回的函數(shù)也將永遠(yuǎn)可以訪問該變量。

在 something 函數(shù)中本地聲明的任何變量都是如此:

 const something = (value) => {
   const r = Math.random();
   const inside = () => {
     // ...
   };
   return inside;
 };
 const first = something('first');
 const second = something('second');
 first(); // logs random number
 second(); // logs another random number

這就像拍攝一些動(dòng)態(tài)場景的照片一樣:只要按下按鈕,整個(gè)場景就會(huì)永遠(yuǎn) “凍結(jié)” 在照片中。下次按下按鈕不會(huì)改變之前拍攝的照片中的任何內(nèi)容。

在 React 中,我們一直都在創(chuàng)建閉包,甚至沒有意識(shí)到,組件內(nèi)聲明的每個(gè)回調(diào)函數(shù)都是一個(gè)閉包:

 const Component = () => {
   const onClick = () => {
     // closure!
   };
   return <button onClick={onClick} />;
 };

useEffect 或 useCallback 鉤子中的所有內(nèi)容都是一個(gè)閉包:

 const Component = () => {
   const onClick = useCallback(() => {
     // closure!
   });
   useEffect(() => {
     // closure!
   });
 };

它們都可以訪問組件中聲明的 state、props 和局部變量:

 const Component = () => {
   const [state, setState] = useState();
   const onClick = useCallback(() => {
     // perfectly fine
     console.log(state);
   });
   useEffect(() => {
     // perfectly fine
     console.log(state);
   });
 };

組件內(nèi)的每個(gè)函數(shù)都是一個(gè)閉包,因?yàn)榻M件本身只是一個(gè)函數(shù)。

過期閉包的問題

但是,以上所有的內(nèi)容,如果你之前沒有接觸過閉包的話會(huì)覺得挺新奇的,但其實(shí)還是挺簡單的,你多創(chuàng)建幾個(gè)函數(shù),就會(huì)變得很自然了。我們寫了這么久的 React 甚至也不需要理解 “閉包” 的概念。

那么問題出在哪里呢?為什么閉包是 JavaScript 中最可怕的東西之一,并讓如此多的開發(fā)者感到痛苦?

因?yàn)橹灰痖]包的函數(shù)存在引用,閉包就會(huì)一直存在。而函數(shù)的引用只是一個(gè)值,可以賦給任何東西。

比如這個(gè)函數(shù),它返回一個(gè)完全無辜的閉包:

 const something = (value) => {
   const inside = () => {
     console.log(value);
   };
   return inside;
 };

問題是每次調(diào)用都會(huì)重新創(chuàng)建內(nèi)部函數(shù),如果我決定嘗試緩存它,會(huì)發(fā)生什么情況呢?類似這樣:

 const cache = {};
 const something = (value) => {
   if (!cache.current) {
     cache.current = () => {
       console.log(value);
     };
   }
   return cache.current;
 };

從表面上看,這段代碼并沒有什么問題。我們只是創(chuàng)建了一個(gè)名為 cache 的外部變量,并將內(nèi)部函數(shù)分配給 cache.current 屬性。然后,我們就不會(huì)再每次都重新創(chuàng)建這個(gè)函數(shù)了,而是直接返回已經(jīng)保存的值。

但是,如果我們嘗試多調(diào)用幾次,就會(huì)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象:

 const first = something('first');
 const second = something('second');
 const third = something('third');
 first(); // logs "first"
 second(); // logs "first"
 third(); // logs "first"

無論我們用不同的參數(shù)調(diào)用多少次 something 函數(shù),記錄的值始終是第一個(gè)參數(shù)!

我們剛剛就創(chuàng)建了一個(gè)所謂的 "過期閉包"。每個(gè)閉包在創(chuàng)建時(shí)都是凍結(jié)的,當(dāng)我們第一次調(diào)用 something 函數(shù)時(shí),我們創(chuàng)建了一個(gè)值變量中包含 "first" 的閉包。然后,我們把它保存在 something 函數(shù)之外的一個(gè)對象中。

當(dāng)我們下一次調(diào)用 something 函數(shù)時(shí),我們將返回之前創(chuàng)建的閉包,而不是創(chuàng)建一個(gè)帶有新閉包的新函數(shù)。這個(gè)閉包會(huì)與 "first" 變量永遠(yuǎn)凍結(jié)在一起。

為了修復(fù)這種問題,我們可以在每次值發(fā)生變化時(shí)重新創(chuàng)建函數(shù)及其閉包,類似這樣:

 const cache = {};
 let prevValue;
 const something = (value) => {
   // check whether the value has changed
   if (!cache.current || value !== prevValue) {
     cache.current = () => {
       console.log(value);
     };
   }
   // refresh it
   prevValue = value;
   return cache.current;
 };

將值保存在變量中,以便我們可以將下一個(gè)值與前一個(gè)值進(jìn)行比較。如果變量發(fā)生了變化,則刷新 cache.current 閉包?,F(xiàn)在,它就會(huì)正確打印變量,如果我們比較具有相同值的函數(shù),比較結(jié)果將返回 true:

 const first = something('first');
 const anotherFirst = something('first');
 const second = something('second');
 first(); // logs "first"
 second(); // logs "second"
 console.log(first === anotherFirst); // will be true

React 中的過期閉包:useCallback

我們剛剛實(shí)現(xiàn)了與 useCallback 鉤子幾乎一模一樣的功能!每次使用 useCallback 時(shí),我們都會(huì)創(chuàng)建一個(gè)閉包,并緩存?zhèn)鬟f給它的函數(shù):

 // that inline function is cached exactly as in the section before
 const onClick = useCallback(() => {
 }, []);

如果我們需要訪問此函數(shù)內(nèi)的 state 或 props,我們需要將它們添加到依賴項(xiàng)數(shù)組中:

 const Component = () => {
   const [state, setState] = useState();
   const onClick = useCallback(() => {
     // access to state inside
     console.log(state);
     // need to add this to the dependencies array
   }, [state]);
 };

這個(gè)依賴關(guān)系數(shù)組會(huì)讓 React 刷新緩存的閉包,就像我們在比較 value !== prevValue 時(shí)所做的一樣。如果我忘記了這個(gè)數(shù)組,我們的閉包就會(huì)過期:

 const Component = () => {
   const [state, setState] = useState();
   const onClick = useCallback(() => {
     // state will always be the initial state value here
     // the closure is never refreshed
     console.log(state);
     // forgot about dependencies
   }, []);
 };

每次我們觸發(fā)該回調(diào)時(shí),所有將被打印的內(nèi)容都是 undefined。

React 中的過期閉包:Refs

在 useCallback 和 useMemo 鉤子之后,引入過期閉包問題的第二個(gè)最常見的方法是 Refs。

如果我嘗試對 onClick 回調(diào)使用 Ref 而不是 useCallback 鉤子,會(huì)發(fā)生什么情況呢?有些文章會(huì)建議通過這樣做來 memoize 組件上的 props。從表面上看,它確實(shí)看起來更簡單:只需將一個(gè)函數(shù)傳遞給 useRef 并通過 ref.current 訪問它,沒有依賴性,不用擔(dān)心。

 const Component = () => {
   const ref = useRef(() => {
     // click handler
   });
   // ref.current stores the function and is stable between re-renders
   return <HeavyComponent onClick={ref.current} />;
 };

然而,組件內(nèi)的每個(gè)函數(shù)都會(huì)形成一個(gè)閉包,包括我們傳遞給 useRef 的函數(shù)。我們的 ref 在創(chuàng)建時(shí)只會(huì)初始化一次,并且不會(huì)自行更新。這基本上就是我們一開始創(chuàng)建的邏輯,只是我們傳遞的不是值,而是我們想要保留的函數(shù)。像這樣:

 const ref = {};
 const useRef = (callback) => {
   if (!ref.current) {
     ref.current = callback;
   }
   return ref.current;
 };

因此,在這種情況下,一開始(即組件剛剛初始化時(shí))形成的閉包將會(huì)被保留,永遠(yuǎn)不會(huì)刷新。當(dāng)我們試圖訪問存儲(chǔ)在 Ref 中的函數(shù)內(nèi)部的 state 或 props 時(shí),我們只能得到它們的初始值:

 const Component = ({ someProp }) => {
   const [state, setState] = useState();
   const ref = useRef(() => {
     // both of them will be stale and will never change
     console.log(someProp);
     console.log(state);
   });
 };

為了解決這個(gè)問題,我們需要確保每次我們試圖訪問的內(nèi)容發(fā)生變化時(shí),ref 值都會(huì)更新。本質(zhì)上,我們需要實(shí)現(xiàn) useCallback 鉤子的依賴數(shù)組所做的事情。

 const Component = ({ someProp }) => {
   // initialize ref - creates closure!
   const ref = useRef(() => {
     // both of them will be stale and will never change
     console.log(someProp);
     console.log(state);
   });
   useEffect(() => {
     // update the closure when state or props change
     ref.current = () => {
       console.log(someProp);
       console.log(state);
     };
   }, [state, someProp]);
 };

React 中的過期閉包:React.memo

最后,我們回到文章的開頭,回到引發(fā)這一切的謎團(tuán)。讓我們再來看看有問題的代碼:

 const HeavyComponentMemo = React.memo(
   HeavyComponent,
   (before, after) => {
     return before.title === after.title;
   },
 );
 const Form = () => {
   const [value, setValue] = useState();
   const onClick = () => {
     // submit our form data here
     console.log(value);
   };
   return (
     <>
       <input
         type="text"
         value={value}
         onChange={(e) => setValue(e.target.value)}
       />
       <HeavyComponentMemo
         title="Welcome to the form"
         onClick={onClick}
       />
     </>
   );
 };

每次點(diǎn)擊按鈕時(shí),都會(huì)打印 "undefined" 。我們在 onClick 中的值從未更新過,你能告訴我為什么嗎?

當(dāng)然,這又是一個(gè)過期閉包。當(dāng)我們創(chuàng)建 onClick 時(shí),首先使用默認(rèn)狀態(tài)值(undefined)形成閉包。我們將該閉包與 title 屬性一起傳遞給我們的 Memo 組件。在比較函數(shù)中,我們只比較了標(biāo)題。它永遠(yuǎn)不會(huì)改變,它只是一個(gè)字符串。比較函數(shù)始終返回 true,HeavyComponent 永遠(yuǎn)不會(huì)更新,因此,它保存的是對第一個(gè) onClick 閉包的引用,并具有凍結(jié)的 undefined 值。

既然我們知道了問題所在,那么該如何解決呢?說起來容易做起來難...

理想情況下,我們應(yīng)該在比較函數(shù)中對每個(gè) props 進(jìn)行比較,因此我們需要在其中加入 onClick:

 (before, after) => {
   return (
     before.title === after.title &&
     before.onClick === after.onClick
   );
 };

不過,在這種情況下,這意味著我們只是重新實(shí)現(xiàn)了 React 的默認(rèn)行為,做的事情與不帶比較函數(shù)的 React.memo 完全一樣。因此,我們可以放棄它,只保留 React.memo (HeavyComponent)。

但這樣做意味著我們需要將 onClick 包裝為 useCallback。但這取決于 state ,我們又回到了原點(diǎn):每次狀態(tài)改變時(shí),我們的 HeavyComponent 都會(huì)重新渲染,這正是我們想要避免的。

我們還可以嘗試很多其他方法,但我們不必進(jìn)行任何大量的重構(gòu)就能擺脫閉包陷阱,有一個(gè)很酷的技巧可以幫助我們。

使用 Refs 逃離閉包陷阱

讓我們暫時(shí)擺脫 React.memo 和 onClick 實(shí)現(xiàn)中的比較函數(shù)。只需一個(gè)具有 state 和 memo 化 HeavyComponent 的 pure component 即可:

 const HeavyComponentMemo = React.memo(HeavyComponent);
 const Form = () => {
   const [value, setValue] = useState();
   return (
     <>
         <input type="text" value={value} onChange={(e) => setValue(e.target.value)} />
         <HeavyComponentMemo title="Welcome to the form" onClick={...} />
     </>
   );
 }

現(xiàn)在我們需要添加一個(gè) onClick 函數(shù),該函數(shù)在重新渲染的時(shí)候會(huì)保持穩(wěn)定,但也可以訪問最新狀態(tài)而無需重新創(chuàng)建。我們將把它存儲(chǔ)在 Ref 中,所以我們暫時(shí)添加一個(gè)空的:

 const Form = () => {
   const [value, setValue] = useState();
   // adding an empty ref
   const ref = useRef();
 };

為了讓函數(shù)能夠訪問最新狀態(tài),每次重新渲染時(shí)都需要重新創(chuàng)建函數(shù),這是無法避免的,這也是閉包的本質(zhì),與 React 無關(guān)。我們應(yīng)該在 useEffect 中修改 Ref,而不是直接在渲染中修改 Ref,所以我們可以這樣做:

 const Form = () => {
   const [value, setValue] = useState();
   // adding an empty ref
   const ref = useRef();
   useEffect(() => {
     // our callback that we want to trigger
     // with state
     ref.current = () => {
       console.log(value);
     };
     // no dependencies array!
   });
 };

不帶依賴數(shù)組的 useEffect 會(huì)在每次重新渲染時(shí)觸發(fā)。這正是我們想要的,所以現(xiàn)在在我們的 ref.current 中,我們有一個(gè)每次重新渲染都會(huì)重新創(chuàng)建的閉包,因此打印的 state 始終是最新的。

但我們不能把 ref.current 直接傳遞給 memoized 組件。每次重新渲染時(shí),這個(gè)值都會(huì)不同, memoization 將無法工作。

 const Form = () => {
   const ref = useRef();
   useEffect(() => {
     ref.current = () => {
       console.log(value);
     };
   });
   return (
     <>
       {/* Can't do that, will break memoization */}
       <HeavyComponentMemo onClick={ref.current} />
     </>
   );
 };

所以,我們創(chuàng)建一個(gè)封裝在 useCallback 中的空函數(shù),并且不依賴于此函數(shù)。

 const Form = () => {
   const ref = useRef();
   useEffect(() => {
     ref.current = () => {
       console.log(value);
     };
   });
   const onClick = useCallback(() => {
     // empty dependency! will never change
   }, []);
   return (
     <>
       {/* Now memoization will work, onClick never changes */}
       <HeavyComponentMemo onClick={onClick} />
     </>
   );
 };

現(xiàn)在,memoization 可以完美地工作,因?yàn)?onClick 從未改變。但有一個(gè)問題:它什么也會(huì)不做。

這里有一個(gè)神奇的竅門:我們只需在 memoized 回調(diào)中調(diào)用 ref.current 即可:

 useEffect(() => {
   ref.current = () => {
     console.log(value);
   };
 });
 const onClick = useCallback(() => {
   // call the ref here
   ref.current();
   // still empty dependencies array!
 }, []);

注意到 ref 并不在 useCallback 的依賴關(guān)系中嗎?ref 本身是不會(huì)改變的。它只是 useRef 鉤子返回的一個(gè)可變對象的引用。但是,當(dāng)閉包凍結(jié)周圍的一切時(shí),并不會(huì)使對象不可變或被凍結(jié)。對象存儲(chǔ)在內(nèi)存的不同部分,多個(gè)變量可以包含對完全相同對象的引用。

 const a = { value: 'one' };
 // b is a different variable that references the same object
 const b = a;

如果我通過其中一個(gè)引用更改對象,然后通過另一個(gè)引用訪問它,更改就會(huì)出現(xiàn):

 a.value = 'ConardLi';
 console.log(b.value); // will be "ConardLi"

在我們的案例中,這種情況并沒有發(fā)生:我們在 useCallback 和 useEffect 中擁有完全相同的引用。因此,當(dāng)我們更改 useEffect 中 ref 對象的 current 屬性時(shí),我們可以在 useCallback 中訪問該屬性,這個(gè)屬性恰好是一個(gè)捕獲了最新狀態(tài)數(shù)據(jù)的閉包。完整代碼如下:

 const Form = () => {
   const [value, setValue] = useState();
   const ref = useRef();
   useEffect(() => {
     ref.current = () => {
       // will be latest
       console.log(value);
     };
   });
   const onClick = useCallback(() => {
     // will be latest
     ref.current?.();
   }, []);
   return (
     <>
       <input
         type="text"
         value={value}
         onChange={(e) => setValue(e.target.value)}
       />
       <HeavyComponentMemo
         title="你好 code秘密花園"
         onClick={onClick}
       />
     </>
   );
 };

現(xiàn)在,我們獲得了兩全其美的結(jié)果:Heavy 組件被適當(dāng)?shù)?memoization,不會(huì)因?yàn)槊看螤顟B(tài)變化而重新渲染。它的 onClick 回調(diào)可以訪問組件中的最新數(shù)據(jù),而不會(huì)破壞 memoization?,F(xiàn)在,我們可以安全地將所需的一切發(fā)送到后端!

最后

下面我們再總結(jié)一下本文中提到的知識(shí)點(diǎn):

  • 每次在另一個(gè)函數(shù)內(nèi)部創(chuàng)建一個(gè)函數(shù)時(shí),都會(huì)形成閉包。
  • 由于 React 組件只是函數(shù),因此內(nèi)部創(chuàng)建的每個(gè)函數(shù)都會(huì)形成閉包,包括 useCallback 和 useRef 等鉤子。
  • 當(dāng)一個(gè)形成閉包的函數(shù)被調(diào)用時(shí),它周圍的所有數(shù)據(jù)都會(huì)被 "凍結(jié)",就像快照一樣。
  • 要更新這些數(shù)據(jù),我們需要重新創(chuàng)建 "閉包" 函數(shù)。這就是使用 useCallback 等鉤子的依賴關(guān)系允許我們做的事情。
  • 如果我們錯(cuò)過了依賴關(guān)系,或者沒有刷新分配給 ref.current 的閉包函數(shù),閉包就會(huì) "過期"。
  • 在 React 中,我們可以利用 Ref 是一個(gè)可變對象這一特性,從而擺脫 "過期閉包" 的問題。我們可以在過期閉包之外更改 ref.current,然后在閉包之內(nèi)訪問它,就可以獲取最新的數(shù)據(jù)。

以上就是詳解如何在React中逃離閉包陷阱的詳細(xì)內(nèi)容,更多關(guān)于React逃離閉包陷阱的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Mybatis(ParameterType)傳遞多個(gè)不同類型的參數(shù)方式

    Mybatis(ParameterType)傳遞多個(gè)不同類型的參數(shù)方式

    這篇文章主要介紹了Mybatis(ParameterType)傳遞多個(gè)不同類型的參數(shù)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java利用反射實(shí)現(xiàn)文件的讀取操作

    Java利用反射實(shí)現(xiàn)文件的讀取操作

    這篇文章主要介紹了Java利用反射實(shí)現(xiàn)文件的讀取操作,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • MybatisPlus?BaseMapper?實(shí)現(xiàn)對數(shù)據(jù)庫增刪改查源碼

    MybatisPlus?BaseMapper?實(shí)現(xiàn)對數(shù)據(jù)庫增刪改查源碼

    MybatisPlus?是一款在?Mybatis?基礎(chǔ)上進(jìn)行的增強(qiáng)?orm?框架,可以實(shí)現(xiàn)不寫?sql?就完成數(shù)據(jù)庫相關(guān)的操作,這篇文章主要介紹了MybatisPlus?BaseMapper?實(shí)現(xiàn)對數(shù)據(jù)庫增刪改查源碼解析,需要的朋友可以參考下
    2023-01-01
  • log4j控制臺(tái)不打印日志故障的詳細(xì)解決方案

    log4j控制臺(tái)不打印日志故障的詳細(xì)解決方案

    這篇文章主要給大家介紹了關(guān)于log4j控制臺(tái)不打印日志故障的詳細(xì)解決方案,log4j不提供默認(rèn)配置,因?yàn)樵谀承┉h(huán)境中可能禁止輸出到控制臺(tái)或文件系統(tǒng),需要的朋友可以參考下
    2023-08-08
  • 基于Feign使用okhttp的填坑之旅

    基于Feign使用okhttp的填坑之旅

    這篇文章主要介紹了基于Feign使用okhttp的填坑之旅,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 一文總結(jié)RabbitMQ中的消息確認(rèn)機(jī)制

    一文總結(jié)RabbitMQ中的消息確認(rèn)機(jī)制

    RabbitMQ消息確認(rèn)機(jī)制指的是在消息傳遞過程中,發(fā)送方發(fā)送消息后,接收方需要對消息進(jìn)行確認(rèn),以確保消息被正確地接收和處理,本文為大家整理了RabbitMQ中的消息確認(rèn)機(jī)制,需要的可以參考一下
    2023-06-06
  • 如何開發(fā)一個(gè)簡單的Akka Java應(yīng)用

    如何開發(fā)一個(gè)簡單的Akka Java應(yīng)用

    這篇文章主要介紹了如何開發(fā)一個(gè)簡單的Akka Java應(yīng)用 ,幫助大家使用Java創(chuàng)建Akka項(xiàng)目并將其打包,感興趣的朋友可以了解下
    2020-10-10
  • Java代碼實(shí)現(xiàn)簡單酒店管理系統(tǒng)

    Java代碼實(shí)現(xiàn)簡單酒店管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了Java代碼實(shí)現(xiàn)簡單酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • java 判斷字符串是否包含子串的方法

    java 判斷字符串是否包含子串的方法

    這篇文章主要介紹了java 判斷字符串是否包含子串的方法的相關(guān)資料,這里提供了三種方法幫助大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下
    2017-08-08
  • java解析Excel的方法(xls、xlsx兩種格式)

    java解析Excel的方法(xls、xlsx兩種格式)

    這篇文章主要介紹了java解析Excel的方法(xls、xlsx兩種格式),需要的朋友可以參考下
    2018-04-04

最新評論