半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

前端

React 17 更新重點 - useEffect 的 clean up 函數時機改變

React 17 更新重點 - useEffect 的 clean up 函數時機改變

(本截圖取自 React 官方部落格)

React 17 已經到了 RC 階段了,官方的部落格聲明這次的發布沒有重大更新或是任何新功能,不過在文章中還是可以找到一些有趣的東西,在這邊一併介紹給大家。

本篇文章不會一一解說每個更新,而是紀錄文章中比較值得關注的地方。

event delegation 的節點改變

在 React 當中,如果使用事件監聽器 <button onClick={}> 的話,React 會將全部的事件監聽器都放在 document 中而非節點本身,這種技巧稱為 Event Delegation,好處是避免大量的事件監聽器造成的效能問題。在 React 17 後,監聽器會改到 root element 而非 document。因為是在 React 的掌握之中,所以之後也比較方便加入新功能,像是推文中提到的 Replay Event。

移除 Event Pooling 機制

為了增進效能,React 17 之前使用了 Event Pooling 機制,也就是自己實作的一套 Synthetic Event,不過根據官方說法,在現代瀏覽器其帶來的效能改進其實有限,並且還會造成一些開發者的困惑。例如 e.target 如果要當作參數送給別的元件使用的話會發現它竟然是 null,這是因為 React 為了提昇效能而作的設計。為了正確使用必須額外呼叫 e.persist() 來確保 React 不會對他上下其手。

在 React 17 後 Event Pooling 的機制全部刪除了,所以也不需要在額外呼叫 e.persist(),讚讚。在某些文章或教學會強調 React 的 Event Pooling 設計,或許未來要改變一下說法。

打星星:useEffect 的 clean up 函數時機改變

這大概是這次改版最重大的改變了。

使用 useEffect 時,因為大部分的 effect 都跟畫面的更新沒有關係,所以 React 會在畫面更新之後再執行 effect。

整體來說流程會像這樣:

元件更新 → DOM 作對應改變 → 畫面更新 → 執行 effect

這避免了 effect 裡頭作的事情因計算量太大可能會影響到 DOM 的繪製。例如:

const App = () => {
  useEffect(() => {
    longlongTask(); // 我會跑很久哦
    console.log('Hello World');
  })
  return <Component />
}

因為上述提到的流程,React 確保了畫面更新後才作其他 effect 避免破壞使用者體驗。

但是有時候某些場景你會希望在畫面更新前執行 effect 來避免畫面的跳動,這時候就可以透過 useLayoutEffect 來代替。

以上都是 React 16 的預期行為,再次複習一遍。接下來要講的是 clean up 的部份。

在 React 16 當中,雖然 useEffect 的 effect 是在畫面更新後執行的,但是 clean up 的函數卻是在畫面更新之前執行。一般來說這沒什麼影響,大部分的 clean up 函數也只是 unsubscribe、取消監聽器或是取消 API 而已。不過如果 clean up 的函數佔用太多時間的話,就會對畫面的更新產生影響。

假設這樣寫:

const App = () => {
  useEffect(() => {
    longlongTask(); // 我會跑很久哦,但沒關係 react 會在畫面更新後才執行
    console.log('Hello World');
    return () => {
      longlongTask(); // 畫面會卡住直到 longlongTask() 結束(在 React 16 當中)
    }
  })
  return <Component />
}

在 React 16 當中,clean up 的執行時機是這樣的(元件更新時):

元件更新 → DOM 作對應改變 → 執行 cleanup 函數 → 畫面更新 → 執行 effect

而在 React 17 之後,clean up 的時機點也改為在畫面更新之後了(元件更新時):

元件更新 → DOM 作對應改變 → 畫面更新 → 執行 cleanup 函數執行 effect

如果是元件 unmount 的話:

React 16:元件更新 → 執行 cleanup 函數 → DOM 作對應改變 → 畫面更新

React 17:元件更新 → DOM 作對應改變 → 畫面更新 → 執行 cleanup 函數

clean up 函數會照 useEffect 順序執行

React 17 executes the cleanup functions in the same order as the effects, according to their position in the tree. Previously, this order was occasionally different.

雖然不知道在怎樣的狀況下 clean up 函數會有順序依賴的需求,不過在 React 16 當中 clean up 執行的時機點似乎偶爾會有非預期的順序。