在您的元件顯示在螢幕上之前,它們必須先由 React 進行渲染。了解此過程中的步驟將幫助您思考程式碼的執行方式並解釋其行為。

您將學習到

  • 渲染在 React 中的意義
  • React 何時以及為何渲染元件
  • 在螢幕上顯示元件的步驟
  • 為何渲染並不總是產生 DOM 更新

想像一下,您的元件是廚房裡的廚師,用食材組裝美味的菜餚。在這個場景中,React 是服務生,負責接收顧客的點餐並將餐點送上桌。這個請求和提供 UI 的過程包含三個步驟:

  1. 觸發渲染(將顧客的訂單送到廚房)
  2. 渲染元件(在廚房準備訂單)
  3. 提交到 DOM(將訂單放在桌上)
  1. React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
    觸發
  2. The Card Chef gives React a fresh Card component.
    渲染
  3. React delivers the Card to the user at their table.
    提交

圖示作者 Rachel Lee Nabors

步驟 1:觸發渲染

元件渲染的原因有兩個:

  1. 這是元件的初始渲染
  2. 元件(或其祖先之一)的狀態已更新

初始渲染

當您的應用程式啟動時,您需要觸發初始渲染。框架和沙盒有時會隱藏這段程式碼,但它是通過使用目標 DOM 節點呼叫 createRoot,然後使用您的元件呼叫其 render 方法來完成的。

import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);

試著將 root.render() 呼叫註解掉,看看元件是否消失!

狀態更新時重新渲染

初始渲染元件後,您可以使用 set 函數 更新其狀態來觸發進一步的渲染。 更新元件的狀態會自動將渲染排入佇列。(您可以將這些想像成餐廳顧客在點餐後,根據他們的口渴或飢餓狀態,點茶、甜點和各種東西。)

  1. React as a server in a restaurant, serving a Card UI to the user, represented as a patron with a cursor for their head. They patron expresses they want a pink card, not a black one!
    狀態更新...
  2. React returns to the Component Kitchen and tells the Card Chef they need a pink Card.
    ...觸發...
  3. The Card Chef gives React the pink Card.
    ...渲染!

圖示作者 Rachel Lee Nabors

步驟 2:React 渲染您的元件

觸發渲染後,React 會呼叫您的元件來決定在螢幕上顯示什麼。「渲染」是指 React 呼叫您的元件

  • 在初始渲染時,React 將呼叫根元件。
  • 對於後續渲染,React 將呼叫觸發渲染的狀態更新的函數元件。

這個過程是遞迴的:如果更新的元件返回其他元件,React 接著會渲染元件,如果該元件也返回其他元件,它會接著渲染元件,依此類推。 這個過程將持續進行,直到沒有更多巢狀元件,且 React 完全知道應該在螢幕上顯示什麼。

在以下範例中,React 將多次呼叫 Gallery()Image()

export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

  • 在初始渲染期間,React 會建立 DOM 節點<section><h1> 和三個<img> 標籤。
  • 在重新渲染期間,React 會計算它們的屬性(如果有)自上次渲染以來是否發生了變化。在下一步(提交階段)之前,它不會使用該資訊執行任何操作。

陷阱

渲染必須始終是純粹的計算

  • 相同的輸入,相同的輸出。給定相同的輸入,組件應始終返回相同的 JSX。(當有人點一份含番茄的沙拉時,他們不應該收到一份含洋蔥的沙拉!)
  • 各司其職。它不應更改渲染前存在的任何物件或變數。(一個訂單不應更改其他任何人的訂單。)

否則,隨著程式碼庫複雜性的增加,您可能會遇到令人困惑的錯誤和不可預測的行為。在「嚴格模式」下開發時,React 會呼叫每個組件的函式兩次,這有助於發現由不純函式引起的錯誤。

深入探討

效能優化

如果更新的組件在樹中非常高,則渲染更新組件中所有巢狀組件的預設行為對於效能而言並非最佳。如果您遇到效能問題,效能一節中描述了幾種選擇性解決方案。 不要過早優化!

步驟 3:React 將變更提交到 DOM

渲染(呼叫)您的組件後,React 將修改 DOM。

  • 對於初始渲染,React 將使用appendChild() DOM API 將其建立的所有 DOM 節點放到螢幕上。
  • 對於重新渲染,React 將應用最少的必要操作(在渲染時計算!)以使 DOM 與最新的渲染輸出匹配。

如果渲染之間沒有差異,React 只會更改 DOM 節點。例如,這裡有一個組件,它每秒都會重新渲染從其父組件傳遞的不同 props。請注意如何在<input>中添加一些文字,更新其value,但在組件重新渲染時文字不會消失

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

這是可行的,因為在最後一步驟中,React 只會使用新的time更新<h1>的內容。它看到<input>出現在 JSX 中的位置與上次相同,因此 React 不會觸碰<input>——或它的value

後記:瀏覽器繪製

渲染完成且 React 更新 DOM 後,瀏覽器將重新繪製螢幕。雖然此過程稱為「瀏覽器渲染」,但我們將其稱為「繪製」,以避免在整個文件中造成混淆。

A browser painting 'still life with card element'.

圖示作者 Rachel Lee Nabors

摘要

  • React 應用程式中的任何螢幕更新均分三個步驟進行
    1. 觸發
    2. 渲染
    3. 提交
  • 您可以使用嚴格模式在組件中查找錯誤
  • 如果渲染結果與上次相同,React 不會觸碰 DOM