Morris's blog

react 16.8後的Hook API是如何運作的

2019-12-15

在 2018 年底的 react16.8 推出之後,我們的function components開始有 hook api 可用,就算是function components也可以有狀態,但有想過為什麼可以這樣嗎?本篇文章是在消化過數個網路上解釋 hook 是如何實作的資源之後再加上我自己的一些想法而產生學習紀錄。

不然也先來想想為什麼 class component 為什麼可以有狀態好了

class component 時代時,我們的 component 是class,它可以被 react 拿去new,每 new 一次就會有一個依照那個 class 而製造出來的實體(instance)產生(在那個 class 裡,某些狀況下,也就是那個 JS 裡的勸退哥[1]this啦), this_in_javascript the template, if you are interested

在這個實體上自然就能夠綁上它的對應屬性[2],能夠用來表現它的狀態-state 跟 行為-method,每一個實體能夠保有自己的狀態[3],所以這在使用上沒有問題。

那換到 function compoent 的時候呢?

一樣以 ListItem 為例,假設今天寫了一個 function component 式的 ListItem component,一個列表裡會有很多個 ListItem,萬一今天每個 item 都必須要有自己的狀態,但是每一個 item 都是同一個 ListItem component 執行之後再經過某種轉換(光是這個轉換就可以寫一篇很大篇的文章,這裡就先不深入了)產生出來的,在 function 的情況下 每個item都必須要有自己的狀態 這件事是無法成立的。雖然 JS 裡的 function 也是 Object, 每個function也能夠有自己的nampspace 如:

function myFunc() {
  // doSomething
}
myFunc.myState = 123 // 但我相信一般人也不會這樣寫,會很混亂...
myFunc.myState === 123 //true

這個myState只會是一個記憶體上的位置,不會是呼叫同一個 function 好幾次並對它 update 後還能夠各自保持各次呼叫時的狀態那樣的狀況。

要做到 同一個 function,但是在不同次的呼叫的時候又能夠存取當下屬於那次呼叫的狀態,也不是做不到, 因為,只要把那個狀態放在function外面,然後在每次執行時知道現在是執行到第幾次,要去那邊取第幾個就行了。 是的,其實就是這麼簡單...。

這些狀態其實就是放在React這個命名空間底下!!! React 底下有一個array專門存放每次用了 hook api 之後傳進去的東西[4],然後還會記住一個index,當我們使用 useState, useEffect這些 API 的時候,這個 index 就會++,所以不管是取值或是改變值,或是 做某些動作(執行傳進useEffect裡的function),都會從那個array裡用那個index取出來。這也是 react 官方文件裡告訴我們 hook 不能夠在if...else跟loop裡使用的原因[5],因為這可能會造成那個 index++錯誤,index 錯誤的話,在從 那個array裡拿出東西時,就會拿錯

這裡附上一個為了讓code一看就懂,超級簡化版的實作:

或者來看看@swyx的完整版本 codesandbox link

reference

Getting Closure on React Hooks by Shawn Wang | JSConf.Asia 2019 @swyx 在2019/07在新加坡舉辦的JSConf asia 2019時的talk,本篇文章裡大多數內容是從這個talk裡得到啟發

  1. Dark Soul 黑魂梗。
  2. OO 概念裡有人講 property, 有人講 field, 有人講 member。
  3. 比說一個 ListItem 各自有自己的state,有很多個 ListItem 時,各自能取到各自的 state,不會取錯。
  4. useState 的時候是 value,useEffect 時就是function(可以想成是在之後某個時間點要執行的 callback)
  5. 因為造成上一次執行這個function時跟這一次執行這個function時取到的 index不同
response