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, 這在react官方文件裡的其中一篇 - implementation-notes中有提到:

If App is a class, the reconciler will instantiate an App with new App(props), 
call the componentWillMount() lifecycle method, 
and then will call the render() method to get the rendered element.

每 new 一次就會有一個依照那個 class 而製造出來的實體(instance)產生(在那個 class 裡,某些狀況下,也就是黑魂梗!!那個 JS 裡的勸退哥this*), this_in_javascript the template, if you are interested

在這個實體上自然就能夠綁上它的OO 概念裡有人講 property, 有人講 field, 有人講 member, 有人講attribute....。對應屬性*,能夠用來表現它的狀態-state 跟 行為-method,比說一個 ListItem 各自有自己的`state`,有很多個 ListItem 時,各自能取到各自的 state,不會取錯。每一個實體能夠保有自己的狀態*,所以這在使用上沒有問題。

那換到 function component 的時候呢?

一樣以 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 <useState 的時候是 value,useEffect 時就是`function`(可以想成是在之後某個時間點要執行的 callback)之後傳進去的東西*,然後還會記住一個index,當我們使用 useState, useEffect這些 API 的時候,這個 index 就會++,所以不管是取值或是改變值,或是 做某些動作(執行傳進useEffect裡的function),都會從那個array裡用那個index取出來。因為造成`上一次執行這個function`時跟`這一次執行這個function`時取到的 index`不同`這也是 react 官方文件裡告訴我們 hook 不能夠在if...else跟loop裡使用的原因*,因為這可能會造成那個 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裡得到啟發

response