欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

react?hooks閉包陷阱切入淺談

 更新時間:2022年07月11日 14:23:44   作者:Chechengyi  
這篇文章主要介紹了從react?hooks閉包陷阱切入淺談react?hooks,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

首先,本文并不會講解 hooks 的基本用法, 本文從 一個hooks中 “奇怪”(其實符合邏輯) 的 “閉包陷阱” 的場景切入,試圖講清楚其背后的因果。同時,在許多 react hooks 奇技淫巧的文章里,也能看到 useRef 的身影,那么為什么使用 useRef 又能擺脫 這個 “閉包陷阱” ? 我想搞清楚這些問題,將能較大的提升對 react hooks 的理解。

react hooks 一出現(xiàn)便受到了許多開發(fā)人員的追捧,或許在使用react hooks 的時候遇到 “閉包陷阱” 是每個開發(fā)人員在開發(fā)的時候都遇到過的事情,有的兩眼懵逼、有的則穩(wěn)如老狗瞬間就定義到了問題出現(xiàn)在何處。

(以下react示范demo,均為react 16.8.3 版本)

你一定遭遇過以下這個場景:

function App(){
    const [count, setCount] = useState(1);
    useEffect(()=>{
        setInterval(()=>{
            console.log(count)
        }, 1000)
    }, [])
}

在這個定時器里面去打印 count 的值,會發(fā)現(xiàn),不管在這個組件中的其他地方使用 setCountcount 設(shè)置為任何值,還是設(shè)置多少次,打印的都是1。是不是有一種,盡管歷經(jīng)千帆,我記得的還是你當(dāng)初的模樣的感覺? hhh... 接下來,我將盡力的嘗試將我理解的,為什么會發(fā)生這么個情況說清楚,并且淺談一些hooks其他的特性。如果有錯誤,希望各位同學(xué)能救救孩子,不要讓我?guī)еe誤的認(rèn)知活下去了。。。

1、一個熟悉的閉包場景

首先從一個各位jser都很熟悉的場景入手。

for ( var i=0; i<5; i++ ) {
    setTimeout(()=>{
        console.log(i)
    }, 0)
}

想寶寶我剛剛畢業(yè)的那一年,這道題還是一道有些熱門的面試題目。而如今...

我就不說為什么最終,打印的都是5的原因了。直接貼出使用閉包打印 0...4的代碼:

for ( var i=0; i<5; i++ ) {
   (function(i){
         setTimeout(()=>{
            console.log(i)
        }, 0)
   })(i)
}

這個原理其實就是使用閉包,定時器的回調(diào)函數(shù)去引用立即執(zhí)行函數(shù)里定義的變量,形成閉包保存了立即執(zhí)行函數(shù)執(zhí)行時 i 的值,異步定時器的回調(diào)函數(shù)才如我們想要的打印了順序的值。

其實,useEffect 的哪個場景的原因,跟這個,簡直是一樣的,useEffect 閉包陷阱場景的出現(xiàn),是 react 組件更新流程以及 useEffect 的實現(xiàn)的自然而然結(jié)果。

2 淺談hooks原理,理解useEffect 的 “閉包陷阱” 出現(xiàn)原因

其實,很不想在寫這篇文章的過程中,牽扯到react原理這方面的東西,因為真的是太整體了(其實主要原因是菜,自己也只是掌握的囫圇吞棗),你要明白這個大概的過程,你得明白支撐起這個大概的一些重要的點。

首先,可能都聽過react的 Fiber 架構(gòu),其實一個 Fiber節(jié)點就對應(yīng)的是一個組件。對于 classComponent 而言,有 state 是一件很正常的事情,F(xiàn)iber對象上有一個 memoizedState 用于存放組件的 state。

ok,現(xiàn)在看 hooks 所針對的 FunctionComponnet。 無論開發(fā)者怎么折騰,一個對象都只能有一個 state 屬性或者 memoizedState 屬性,可是,誰知道可愛的開發(fā)者們會在 FunctionComponent 里寫上多少個 useState,useEffect 等等 ? 所以,react用了鏈表這種數(shù)據(jù)結(jié)構(gòu)來存儲 FunctionComponent 里面的 hooks。比如:

function App(){
    const [count, setCount] = useState(1)
    const [name, setName] = useState('chechengyi')
    useEffect(()=>{
    }, [])
    const text = useMemo(()=>{
        return 'ddd'
    }, [])
}

在組件第一次渲染的時候,為每個hooks都創(chuàng)建了一個對象

type Hook = {
  memoizedState: any,
  baseState: any,
  baseUpdate: Update<any, any> | null,
  queue: UpdateQueue<any, any> | null,
  next: Hook | null,
};

最終形成了一個鏈表。

這個對象的memoizedState屬性就是用來存儲組件上一次更新后的 state,next毫無疑問是指向下一個hook對象。在組件更新的過程中,hooks函數(shù)執(zhí)行的順序是不變的,就可以根據(jù)這個鏈表拿到當(dāng)前hooks對應(yīng)的Hook對象,函數(shù)式組件就是這樣擁有了state的能力。當(dāng)前,具體的實現(xiàn)肯定比這三言兩語復(fù)雜很多。

所以,知道為什么不能將hooks寫到if else語句中了把?因為這樣可能會導(dǎo)致順序錯亂,導(dǎo)致當(dāng)前hooks拿到的不是自己對應(yīng)的Hook對象。

useEffect 接收了兩個參數(shù),一個回調(diào)函數(shù)和一個數(shù)組。數(shù)組里面就是 useEffect 的依賴,當(dāng)為 [] 的時候,回調(diào)函數(shù)只會在組件第一次渲染的時候執(zhí)行一次。如果有依賴其他項,react 會判斷其依賴是否改變,如果改變了就會執(zhí)行回調(diào)函數(shù)。說回最初的場景:

function App(){
    const [count, setCount] = useState(1);
    useEffect(()=&gt;{
        setInterval(()=&gt;{
            console.log(count)
        }, 1000)
    }, [])
    function click(){ setCount(2) }
}

好,開動腦袋開始想象起來,組件第一次渲染執(zhí)行 App(),執(zhí)行 useState 設(shè)置了初始狀態(tài)為1,所以此時的 count 為1。然后執(zhí)行了 useEffect,回調(diào)函數(shù)執(zhí)行,設(shè)置了一個定時器每隔 1s 打印一次 count。

接著想象如果 click 函數(shù)被觸發(fā)了,調(diào)用 setCount(2) 肯定會觸發(fā)react的更新,更新到當(dāng)前組件的時候也是執(zhí)行 App(),之前說的鏈表已經(jīng)形成了哈,此時 useStateHook 對象 上保存的狀態(tài)置為2, 那么此時 count 也為2了。然后在執(zhí)行 useEffect 由于依賴數(shù)組是一個空的數(shù)組,所以此時回調(diào)并不會被執(zhí)行。

ok,這次更新的過程中根本就沒有涉及到這個定時器,這個定時器還在堅持的,默默的,每隔1s打印一次 count。 注意這里打印的 count ,是組件第一次渲染的時候 App() 時的 count, count的值為1,因為在定時器的回調(diào)函數(shù)里面被引用了,形成了閉包一直被保存。

2 難道真的要在依賴數(shù)組里寫上的值,才能拿到新鮮的值?

仿佛都習(xí)慣性都去認(rèn)為,只有在依賴數(shù)組里寫上我們所需要的值,才能在更新的過程中拿到最新鮮的值。那么看一下這個場景:

function App() {
  return <Demo1 />
}
function Demo1(){
  const [num1, setNum1] = useState(1)
  const [num2, setNum2] = useState(10)
  const text = useMemo(()=>{
    return `num1: ${num1} | num2:${num2}`
  }, [num2])
  function handClick(){
    setNum1(2)
    setNum2(20)
  }
  return (
    <div>
      {text}
      <div><button onClick={handClick}>click!</button></div>
    </div>
  )
}

text 是一個 useMemo ,它的依賴數(shù)組里面只有num2,沒有num1,卻同時使用了這兩個state。當(dāng)點擊button 的時候,num1和num2的值都改變了。那么,只寫明了依賴num2的 text 中能否拿到 num1 最新鮮的值呢?

如果你裝了 react 的 eslint 插件,這里也許會提示你錯誤,因為在text中你使用了 num1 卻沒有在依賴數(shù)組中添加它。 但是執(zhí)行這段代碼會發(fā)現(xiàn),是可以正常拿到num1最新鮮的值的。

如果理解了之前第一點說的“閉包陷阱”問題,肯定也能理解這個問題。

為什么呢,再說一遍,這個依賴數(shù)組存在的意義,是react為了判定,在本次更新中,是否需要執(zhí)行其中的回調(diào)函數(shù),這里依賴了的num2,而num2改變了?;卣{(diào)函數(shù)自然會執(zhí)行, 這時形成的閉包引用的就是最新的num1和num2,所以,自然能夠拿到新鮮的值。問題的關(guān)鍵,在于回調(diào)函數(shù)執(zhí)行的時機,閉包就像是一個照相機,把回調(diào)函數(shù)執(zhí)行的那個時機的那些值保存了下來。之前說的定時器的回調(diào)函數(shù)我想就像是一個從1000年前穿越到現(xiàn)代的人,雖然來到了現(xiàn)代,但是身上的血液、頭發(fā)都是1000年前的。

3 為什么使用useRef能夠每次拿到新鮮的值?

大白話說:因為初始化的 useRef 執(zhí)行之后,返回的都是同一個對象。寫到這里寶寶又不禁回憶起剛學(xué)js那會兒,捧著紅寶書啃時候的場景了:

var A = {name: 'chechengyi'}
var B = A
B.name = 'baobao'
console.log(A.name) // baobao

對,這就是這個場景成立的最根本原因。

也就是說,在組件每一次渲染的過程中。 比如 ref = useRef() 所返回的都是同一個對象,每次組件更新所生成的ref指向的都是同一片內(nèi)存空間, 那么當(dāng)然能夠每次都拿到最新鮮的值了。犬夜叉看過把?一口古井連接了現(xiàn)代世界與500年前的戰(zhàn)國時代,這個同一個對象也將這些個被保存于不同閉包時機的變量了聯(lián)系了起來。

使用一個例子或許好理解一點:

    /* 將這些相關(guān)的變量寫在函數(shù)外 以模擬react hooks對應(yīng)的對象 */
	let isC = false
	let isInit = true; // 模擬組件第一次加載
	let ref = {
		current: null
	}
	function useEffect(cb){
		// 這里用來模擬 useEffect 依賴為 [] 的時候只執(zhí)行一次。
 		if (isC) return
		isC = true	
		cb()	
	}
	function useRef(value){
		// 組件是第一次加載的話設(shè)置值 否則直接返回對象
		if ( isInit ) {
			ref.current = value
			isInit = false
		}
		return ref
	}
	function App(){
		let ref_ = useRef(1)
		ref_.current++
		useEffect(()=>{
			setInterval(()=>{
				console.log(ref.current) // 3
			}, 2000)
		})
	}
		// 連續(xù)執(zhí)行兩次 第一次組件加載 第二次組件更新
	App()
	App()

所以,提出一個合理的設(shè)想。只要我們能保證每次組件更新的時候,useState 返回的是同一個對象的話?我們也能繞開閉包陷阱這個情景嗎? 試一下吧。

function App() {
  // return <Demo1 />
  return <Demo2 />
}
function Demo2(){
  const [obj, setObj] = useState({name: 'chechengyi'})
  useEffect(()=>{
    setInterval(()=>{
      console.log(obj)
    }, 2000)
  }, [])
  function handClick(){
    setObj((prevState)=> {
      var nowObj = Object.assign(prevState, {
        name: 'baobao',
        age: 24
      })
      console.log(nowObj == prevState)
      return nowObj
    })
  }
  return (
    <div>
      <div>
        <span>name: {obj.name} | age: {obj.age}</span>
        <div><button onClick={handClick}>click!</button></div>
      </div>
    </div>
  )
}

簡單說下這段代碼,在執(zhí)行 setObj 的時候,傳入的是一個函數(shù)。這種用法就不用我多說了把?然后 Object.assign 返回的就是傳入的第一個對象??們貉灾褪窃谠O(shè)置的時候返回了同一個對象。

執(zhí)行這段代碼發(fā)現(xiàn),確實點擊button后,定時器打印的值也變成了:

{
    name: 'baobao',
    age: 24 
}

4 完畢

通過一次“閉包陷阱” 淺談 react hooks 全文再此就結(jié)束了。 反正寫完了這篇文章,我對 hooks 的認(rèn)識是比以前深了,更多關(guān)于react hooks閉包的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解超簡單的react服務(wù)器渲染(ssr)入坑指南

    詳解超簡單的react服務(wù)器渲染(ssr)入坑指南

    這篇文章主要介紹了詳解超簡單的react服務(wù)器渲染(ssr)入坑指南,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • 解決jest處理es模塊示例詳解

    解決jest處理es模塊示例詳解

    這篇文章主要為大家介紹了解決jest處理es模塊示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • 如何在React項目中優(yōu)雅的使用對話框

    如何在React項目中優(yōu)雅的使用對話框

    在項目中,對話框和確認(rèn)框是使用頻率很高的組件,下面這篇文章主要給大家介紹了關(guān)于如何在React項目中優(yōu)雅的使用對話框的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • 詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter

    詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter

    這篇文章主要介紹了詳解react-router 4.0 下服務(wù)器如何配合BrowserRouter,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • React組件中監(jiān)聽函數(shù)獲取不到最新的state問題

    React組件中監(jiān)聽函數(shù)獲取不到最新的state問題

    這篇文章主要介紹了React組件中監(jiān)聽函數(shù)獲取不到最新的state問題問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • react腳手架構(gòu)建運行時報錯問題及解決

    react腳手架構(gòu)建運行時報錯問題及解決

    這篇文章主要介紹了react腳手架構(gòu)建運行時報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • react?diff?算法實現(xiàn)思路及原理解析

    react?diff?算法實現(xiàn)思路及原理解析

    這篇文章主要介紹了react?diff?算法實現(xiàn)思路及原理解析,本節(jié)我們正式進入基本面試必考的核心地帶?--?diff?算法,了解如何優(yōu)化和復(fù)用?dom?操作的,還有我們常見的?key?的作用,需要的朋友可以參考下
    2022-05-05
  • react-native組件中NavigatorIOS和ListView結(jié)合使用的方法

    react-native組件中NavigatorIOS和ListView結(jié)合使用的方法

    這篇文章主要給大家介紹了關(guān)于react-native組件中NavigatorIOS和ListView結(jié)合使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-09-09
  • react?redux的原理以及基礎(chǔ)使用講解

    react?redux的原理以及基礎(chǔ)使用講解

    這篇文章主要介紹了react?redux的原理以及基礎(chǔ)使用講解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React使用PropTypes實現(xiàn)類型檢查功能

    React使用PropTypes實現(xiàn)類型檢查功能

    這篇文章主要介紹了React高級指引中使用PropTypes實現(xiàn)類型檢查功能的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-02-02

最新評論