透過 children props 解決 React 渲染浪費的效能問題

閱讀時間約 13 分鐘

一粥一飯,當思來處不易。

從小我們便被如此諄諄教誨,浪費就是 bad,應愛惜手上擁有的資源。

而在軟體開發的世界,用越少的資源完成越多的功能,絕對是大家爭先搶後的應許之地,因此效能優化永遠停在十八歲,那最令人心神嚮往的狀態。

再把視野聚焦在 React 開發上,我們知道,每當原始資料更新,其所綁定的元件 (component) 函式會被呼叫,進行渲染 (render)。

雖然 React 的渲染僅止於元件函式呼叫,並未包含真實 DOM 的重繪,但仍是一筆可觀的效能開銷。此外,隨著應用程式複雜化,很容易發生沒必要更新 DOM 的元件,在控管不到位的狀況下也跟著進行渲染,造成浪費。

這篇學習筆記會記錄 children props 這個老朋友如何實現效能優化。不過在那之前,我們先來溫習一下,React 會在什麼情況下進行重新渲染,畢竟效能浪費時常出現在這個環節。


元件實體 (component instance) 何時會重新渲染?

如果對於元件實體 (component instance) 不熟悉,這邊先簡單溫習一下。在 React 中,元件 (component) 就像建築藍圖,而這份建築藍圖可以用在各種地方蓋房子,建立元件實體。實體內含的狀態資料 (state) 和事件處理函式等等,全部都是獨立互不影響的。

舉個例子,我們定義了一個名為 Button 的元件,但可以在不同的地方建立該元件的實體:

function Button() {
return <button>我是個社畜按鈕</button>;
}

function App() {
<ul>
<li>
<Button> // 獨立的元件實體
</li>
<li>
<Button> // 獨立的元件實體
</li>
<li>
<Button> // 獨立的元件實體
</li>
</ul>
}


了解元件實體的概念後,讓我們回到重新渲染的主題上。

一個元件實體,通常會在以下三種情況下被重新渲染:

  • state 更新
  • context 更新
  • 父層元件重新渲染

頭兩種狀況比較好理解,但第三種狀況需要留意一下。曾幾何時,我和其他很多人一樣,以為 props 是造成元件重新渲染的來源之一,但其實不然。

追根究柢,是因為父層元件重新渲染了,才造成子層的元件們一同重新渲染。這是 React 起初設計便有的機制,符合單向資料流和 virtual dom 的運作。

props 正好就是從父層傳遞到子層元件的資料,因此容易產生是因為 props 改變層才引發子層元件重新渲染的錯覺。

以上三種情況可說是 React 元件重新渲染的觸發點,觸發之後,React 內部會開始 diffing,也就是比對本次渲染所產生的 virtual dom 和上一次渲染所產生的 virtual dom 有什麼區別。區別之處,就是最後請 DOM 更新的部分。

然而正如先前所提,若我們重新渲染的元件,其實壓根沒有經歷任何變化,那該次的比對可說是徒勞無功,形成被浪費的渲染 (wasted render)

該次渲染沒有產生任何的 DOM 變更

對於小規模的應用程式來說,這不是什麼大問題。然而對於渲染頻繁或是元件緩慢的應用程式,我們不得不正襟危坐,好好正視渲染浪費的問題。


Children props 竟然可以拿來優化效能?!

children props 是一種特別的 props,讓元件接受任何內容當作子元素,內容可以是 JSX 元素、字串、函式,或甚至其他元件。

以往我都把 children props 當作 component composition 的其中一塊拼圖,解決 prop drilling 的問題。但其實透過 chidren props,我們就可以達到效能優化。

以下提供一個範例:

import { useState } from "react";

function SlowComponent() {
const words = Array.from({ length: 100_000 }, () => "WORD");
return (
<ul>
{words.map((word, i) => (
<li key={i}>
{i}: {word}
</li>
))}
</ul>
);
}

export default function Test() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Slow counter?!?</h1>
<button onClick={() => setCount((c) => c + 1)}>Increase: {count}</button>
<SlowComponent />
</div>
);
}


SlowComponent 被呼叫後,會產出一份巨大的清單,內含十萬個 <li> 元素。而它同時也是 Test 的子元件。

Test 元件除了 SlowComponent 之外,還回傳了一個 h1 元素以及一個按鈕。按鈕綁定了點擊處理事件,每點擊一下,便透過 setCount 改變 count 的狀態資料,然後顯示成按鈕文字。

非常容易理解,運作起來也沒有問題,但經過前面的回顧,我們知道當父層元件重新渲染,其麾下的子層元件也將跟著重新渲染,所以每當使用者點擊按鈕, SlowComponent 都會隨著父層元件 Test 重新渲染。

問題就這麼出現了:

  1. SlowComponent 沒有任何資料更新,根本不必隨著父層元件重新渲染
  2. SlowComponent 產生的 <li> 多達十萬個,造成按鈕 UI 的數字更新延遲


所以該怎麼辦呢?

直覺的想法,是把按鈕獨立成 Button 元件,然後將 count state 移動到 Button 裡面。如此一來,SlowComponent 便不會在 count state 更新後,連同 Button 重新渲染。進一步來說,更外層的 Test 元件也不會重新渲染了。

import { useState } from "react";

function SlowComponent() {
// If this is too slow on your maching, reduce the `length`
const words = Array.from({ length: 100_000 }, () => "WORD");
return (
<ul>
{words.map((word, i) => (
<li key={i}>
{i}: {word}
</li>
))}
</ul>
);
}

// 獨立出 Button 元件​
function Button() {
const [count, setCount] = useState(0);

return (
<button onClick={() => setCount((c) => c + 1)}>Increase: {count}</button>
);
}

export default function Test() {
return (
<div>
<h1>Slow counter?!?</h1>
<Button /> // 使用 Button 元件
<SlowComponent />
</div>
);
}


這麼做的確有達到目的,但如果今天 state 需要在 SlowComponent 的更上層被使用呢?那可就無法將使用 state 和不使用 state 的元件拆分獨立了 😥

export default function App() {
let [color, setColor] = useState('red');
return (
<div style={{ color }}> // color state 需要在 SlowComponent 上層被使用
<input value={color} onChange={(e) => setColor(e.target.value)} />
<p>Hello, world!</p>
<SlowComponent /> // 罪魁禍首
</div>
);
}


遇到這種狀況,children props 便能發揮作用!

我們將原先 App 元件的 color state 和其他相關 JSX 移動到新建立的 ColorPicker,然後透過 children props,將 <p> 元素以及重頭戲 SlowComponent 傳遞進去。

export default function App() {
return (
<ColorPicker>
<p>Hello, world!</p>
<SlowComponent />
</ColorPicker>
);
}

function ColorPicker({ children }) {
let [color, setColor] = useState("red");
return (
<div style={{ color }}>
<input value={color} onChange={(e) => setColor(e.target.value)} />
{children}
</div>
);
}


可、可是之前不是說父層元件會帶動子層元件一起重新渲染嗎?這樣當 ColorPicker 因為 color state 更新而重新渲染,SlowComponent 也會跟著一起......

先別著急,我們再看仔細些,SlowComponent 其實一直待在 App 元件裡面,它是以 JSX 內容的形式,被傳遞到 ColorPicker ,也就是我們熟悉的 children props。

children props 裡面有個「子」含意的 children,容易與父層元件 vs 子層元件混淆,但定下心好好想一下,整個重新渲染的流程會變成這樣:

  1. color state 更新,而它是 ColorPicker 的 local state,所以造成 ColorPicker 重新渲染
  2. ColorPicker 重新渲染,需要接取 children props 當作渲染的資料來源之一
  3. children 是從 App 傳遞下來的 props,而 App 並沒有經歷重新渲染,所以傳遞下來的 children props,和上一回渲染的 children props 內容相同。
  4. children props 內容之一的 SlowComponent,因為上一步驟的理由,被 React 判定不需要重新渲染
  5. 透過 children props 原本的運作機制,我們可以巧妙運用來節省掉不必要的渲染 ✨
  6. 補充:針對第三步驟的 children props 內容,可以參考這篇文章的詳細說明。
  7. 補充資料
  8. The mystery of React Element, children, parents and re-renders
  9. Before You memo()
  10. React components - when do children re-render?
  11. The Ultimate React Course 2024: React, Redux & More
18會員
34Content count
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
留言0
查看全部
發表第一個留言支持創作者!
蕭宇廷的沙龍 的其他內容
透過 Netlify function 提供的 FaaS 服務,解決無法佈署 Json-Server 以及其他 live server 到 Netlify 的問題。
介紹如何透過 Web API 的 AbortController 來解決 React useEffect 中的 fetch 請求競態條件 (race condition)。
本文記錄了在實作Movie Guide專案中嘗試使用useEffect Hook從OMDB API抓取電影資料的過程。透過錯誤的抓資料方式和正確使用方式的對比,介紹了useEffect Hook的基本語法、與async...await的搭配、依賴陣列的使用以及cleanup函式的重要性。
React props 的基本概念與用法,同時也提到了一點 React 在資料傳遞方面的規定與原則。
透過 Netlify function 提供的 FaaS 服務,解決無法佈署 Json-Server 以及其他 live server 到 Netlify 的問題。
介紹如何透過 Web API 的 AbortController 來解決 React useEffect 中的 fetch 請求競態條件 (race condition)。
本文記錄了在實作Movie Guide專案中嘗試使用useEffect Hook從OMDB API抓取電影資料的過程。透過錯誤的抓資料方式和正確使用方式的對比,介紹了useEffect Hook的基本語法、與async...await的搭配、依賴陣列的使用以及cleanup函式的重要性。
React props 的基本概念與用法,同時也提到了一點 React 在資料傳遞方面的規定與原則。
你可能也想看
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
過去因著完美主義拖延, 讓我無法達成想要的目標, 每當決定做一件事,我會不停的懷疑自己、猶豫不決。 想要準備到完美,但卻無法跨出那一步。 ​ 明明很在乎的事情,卻反而更拖延。 因為害怕努力後的成果,不是理想的樣子, 害怕沒有符合別人以及自己的期待。 它甚至讓我焦慮、胃痛,
Thumbnail
對於網路寫作者、部落格主或是內容創作者而言,這篇文章或許是你需要的。 系列文創作不僅解決了「單篇文章缺乏深度」的問題,還能有效提升網站或個人品牌的流量。 這是我在實踐「系列文創作」之後,獲得的三大體悟。
Thumbnail
根據機車近兩年汰舊換新的數據,我們發現電車所遇到的成長瓶頸-思考如何解決價格以外的痛點。價格補貼確實吸引到價格敏感的客群,但是那些不為所動的民眾,在觀望些什麼呢?有什麼是「油車有」但「電車沒有」而「消費者很在乎」的「關鍵因素」?只要電車尚未解決那些痛點,整體換購的意願就很難提高。
Thumbnail
胸部是展現女人味的部位,也是讓穿衣能夠凸顯身形線條的重要關鍵,因此今天要來帶大家了解胸小只能靠隆乳?加胸墊的解方嗎?香氛診療豐胸專家教你透過嗅覺搭配穴點刺激能自然更提升罩杯與改善上胸無肉,一起來瞧瞧有哪些豐胸配方吧! 胸部造成的困擾 許多女孩總擔心穿平口背心副乳會外露或胸型不夠堅挺,甚至因為對外表產
Thumbnail
如何有效練題?透過提問力和思考,把知識點背後的思考串連成線,就可看到此題的解題思路。大腦不喜歡你努力死記,它喜歡你邏輯串連。思考可視化的筆記,是一道題的解題筆記,也是卡片筆記,更是全息圖,也能是知識圖卡,最後更可以是動態解題影片。《光輝模板》實作紀錄。
Thumbnail
陶卉在雲水戲劇工作坊劉飛雲(阿招)團長的支持下,在永和開班授課,如今已辦了9期,每一期都有二十多名的學生。陶卉也會帶著學生登臺表演、驗收成果,讓參加的學生從越劇中獲得樂趣與成就感。 圖文提供=新北市文化局.社區營造一般性補助計畫 臺灣媳婦在《一鳴驚人》拿下銀獎 每週二晚上免費授課,提供新住民交流
Thumbnail
透過機器學習與統計分析角度分析UCI機器學習資料集中的網路顧客購買意圖資料,並透過決策樹方式預測顧客是否會購買產品,提及少部分特徵選取、資料不平衡問題。
Thumbnail
言語是內在本質的外在表現,儘管說話的技巧可以培養,但是一個人的心性,卻是用再美的巧語包裝,也終究會隨著時間顯化。
Thumbnail
每個人都想得到幸福,但是我們知道如何得到幸福嗎? 如果有人可以教導我們得到幸福的方法,該有多好呢! 這本書《給想整理房間,也想整理人生的你》,便是創造了一個故事。讓代表幸福的「座敷童子」與代表不幸的「窮神」來告訴讀者,什麼樣的環境會吸引座敷童子,什麼樣的環境又會吸引窮神進駐。 不幸的兩大場景:「內
Thumbnail
我感覺到我的大腿的痠,那個痠要怎麼用文字形容?實在是很難啊。我又想到有個學員寫,在寫自己身體部位的時候,才發現自己都不確定那些部位叫什麼名字。對呀,就像我現在只能說我的大腿肌肉痠,但到底是哪一條在痠,叫什麼名字?都講不出來。不過講不出來也沒關係,重點是我現在在寫的時候,我又感覺了一次昨天晚上的痠。
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
過去因著完美主義拖延, 讓我無法達成想要的目標, 每當決定做一件事,我會不停的懷疑自己、猶豫不決。 想要準備到完美,但卻無法跨出那一步。 ​ 明明很在乎的事情,卻反而更拖延。 因為害怕努力後的成果,不是理想的樣子, 害怕沒有符合別人以及自己的期待。 它甚至讓我焦慮、胃痛,
Thumbnail
對於網路寫作者、部落格主或是內容創作者而言,這篇文章或許是你需要的。 系列文創作不僅解決了「單篇文章缺乏深度」的問題,還能有效提升網站或個人品牌的流量。 這是我在實踐「系列文創作」之後,獲得的三大體悟。
Thumbnail
根據機車近兩年汰舊換新的數據,我們發現電車所遇到的成長瓶頸-思考如何解決價格以外的痛點。價格補貼確實吸引到價格敏感的客群,但是那些不為所動的民眾,在觀望些什麼呢?有什麼是「油車有」但「電車沒有」而「消費者很在乎」的「關鍵因素」?只要電車尚未解決那些痛點,整體換購的意願就很難提高。
Thumbnail
胸部是展現女人味的部位,也是讓穿衣能夠凸顯身形線條的重要關鍵,因此今天要來帶大家了解胸小只能靠隆乳?加胸墊的解方嗎?香氛診療豐胸專家教你透過嗅覺搭配穴點刺激能自然更提升罩杯與改善上胸無肉,一起來瞧瞧有哪些豐胸配方吧! 胸部造成的困擾 許多女孩總擔心穿平口背心副乳會外露或胸型不夠堅挺,甚至因為對外表產
Thumbnail
如何有效練題?透過提問力和思考,把知識點背後的思考串連成線,就可看到此題的解題思路。大腦不喜歡你努力死記,它喜歡你邏輯串連。思考可視化的筆記,是一道題的解題筆記,也是卡片筆記,更是全息圖,也能是知識圖卡,最後更可以是動態解題影片。《光輝模板》實作紀錄。
Thumbnail
陶卉在雲水戲劇工作坊劉飛雲(阿招)團長的支持下,在永和開班授課,如今已辦了9期,每一期都有二十多名的學生。陶卉也會帶著學生登臺表演、驗收成果,讓參加的學生從越劇中獲得樂趣與成就感。 圖文提供=新北市文化局.社區營造一般性補助計畫 臺灣媳婦在《一鳴驚人》拿下銀獎 每週二晚上免費授課,提供新住民交流
Thumbnail
透過機器學習與統計分析角度分析UCI機器學習資料集中的網路顧客購買意圖資料,並透過決策樹方式預測顧客是否會購買產品,提及少部分特徵選取、資料不平衡問題。
Thumbnail
言語是內在本質的外在表現,儘管說話的技巧可以培養,但是一個人的心性,卻是用再美的巧語包裝,也終究會隨著時間顯化。
Thumbnail
每個人都想得到幸福,但是我們知道如何得到幸福嗎? 如果有人可以教導我們得到幸福的方法,該有多好呢! 這本書《給想整理房間,也想整理人生的你》,便是創造了一個故事。讓代表幸福的「座敷童子」與代表不幸的「窮神」來告訴讀者,什麼樣的環境會吸引座敷童子,什麼樣的環境又會吸引窮神進駐。 不幸的兩大場景:「內
Thumbnail
我感覺到我的大腿的痠,那個痠要怎麼用文字形容?實在是很難啊。我又想到有個學員寫,在寫自己身體部位的時候,才發現自己都不確定那些部位叫什麼名字。對呀,就像我現在只能說我的大腿肌肉痠,但到底是哪一條在痠,叫什麼名字?都講不出來。不過講不出來也沒關係,重點是我現在在寫的時候,我又感覺了一次昨天晚上的痠。