React Doc | Describing the UI - Conditional Rendering

picture

2025-01-23

React Doc | Describing the UI - Conditional Rendering

前言

此為 Describing the UI: Unveiling the Power of React Components 此篇文章的延伸內容之一。

這一系列文章是筆者在學習 React 時,閱讀 React 官方文件 所做的翻譯筆記,希望能幫助更多的人學習 React。

筆者會將段落標題的英文原文附上在標題後方,讓有需要的人可以參照。

本篇文章沒有節錄原文範例的輸出結果,如有需要請至原文對照參考。

條件式渲染 (Conditional Rendering)

你的元件經常需要根據不同的條件顯示不同的內容。在 React 中,可以使用 JavaScript 語法(例如 if 語句、&& 以及 ? : 運算子)來進行條件式渲染。

這個章節你將學到:

  • 如何根據條件回傳不同的 JSX
  • 如何條件式地包含或排除某一段 JSX
  • React 程式碼中常見的條件語法捷徑

根據條件回傳 JSX

假設你有一個 PackingList 元件,用來渲染幾個可以被標記為已打包或未打包的 Item 物品:

function Item({ name, isPacked }) {
  return <li className='item'>{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

可以注意到這裡,有些 Item 元件的 isPacked 屬性被設定為 true,而不是 false

如果是 isPacked={true},意思就是這是已經打包的物品,我們用 isPacked 來表示是否已經打包。

現在我們想在畫面上為已打包的項物品新增一個勾選標記(✅),我們可以使用 if/else 語句 這樣寫:

if (isPacked) {
  return <li className='item'>{name} ✅</li>;
}
return <li className='item'>{name}</li>;

如果 isPacked 屬性為 true,這段程式碼 回傳一個不同的 JSX 樹。透過這個改動,部分物品會在最後面顯示一個勾選標記(✅):

function Item({ name, isPacked }) {
  if (isPacked) {
    return <li className='item'>{name} ✅</li>;
  }
  return <li className='item'>{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

試著編輯在每種情況下回傳的內容,觀察結果的變化!

注意如何使用 JavaScript 的 ifreturn 語句建立分之邏輯。因為在 React 中,控制流程(例如條件)是由 JavaScript 處理的。

使用 null 條件式地不回傳任何內容

在某些情況下,你可能不想渲染任何內容。例如,假設你完全不想顯示已打包的物品。

而一個元件必須回傳一些內容,而在這種情況下,你可以回傳 null

if (isPacked) {
  return null;
}
return <li className='item'>{name}</li>;

如果 isPackedtrue,這個元件將回傳空的內容,也就是 null。否則,它會回傳要渲染的 JSX

function Item({ name, isPacked }) {
  if (isPacked) {
    return null;
  }
  return <li className='item'>{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

實際上,從元件回傳 null 並不常見,因為這可能會讓嘗試渲染該元件的開發者感到困惑。更常見的做法是,在父元件的 JSX 中有條件地包含或排除該元件。以下是實現方法!

根據條件包含 JSX

在前面的範例中,你控制了元件會回傳哪個 JSX 樹(如果有的話)。你可能已經注意到渲染輸出中有些重複:

<li className='item'>{name} ✅</li>

與以下內容非常相似:

<li className='item'>{name}</li>

這兩個條件分支都回傳了 <li className="item">...</li>

if (isPacked) {
  return <li className='item'>{name} ✅</li>;
}

return <li className='item'>{name}</li>;

雖然這種重複並不會造成傷害,但它可能會讓你的程式碼變得難以維護。如果你想更改 className,就必須在程式碼的兩個地方進行修改!在這種情況下,你可以有條件地包含少量的 JSX,讓程式碼更加DRY(避免重複)。

條件(三元)運算子 (? :)

JavaScript 提供了一種簡潔的語法來撰寫條件運算式 —— 條件運算子 或稱為三元運算子。

與其這樣寫:

if (isPacked) {
  return <li className='item'>{name} ✅</li>;
}
return <li className='item'>{name}</li>;

你可以這樣寫:

return <li className='item'>{isPacked ? name + " ✅" : name}</li>;

你可以將它理解為:「如果 isPacked 為 true,則(?)渲染 name + ' ✅',否則(:)渲染 name」。

💡 深入探討 - 這兩個範例是完全等價的嗎?如果你有物件導向程式設計的背景,可能會假設上述兩個範例存在微妙的差異,因為其中一個可能會創造兩個不同的 <li>「實例」。但事實上,JSX 元素並不是「實例」,因為它們不包含任何內部狀態,也不是實際的 DOM 節點。它們更像是輕量的描述,就像藍圖一樣。因此,這兩個範例其實是完全等價的。在 保留與重置狀態 這篇文章有更詳細地解釋其運作原理。(也可以等待筆者日後翻譯此文)

現在,假設你想將已完成項目的文字包裹在另一個 HTML 標籤中,例如使用 <del> 來加上刪除線。你可以新增更多的換行和括號,讓在每個情況下嵌套更多 JSX 時變得更容易:

function Item({ name, isPacked }) {
  return <li className='item'>{isPacked ? <del>{name + " ✅"}</del> : name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

這種風格適合處理簡單的條件,但要適度使用。如果你的元件因為過多巢狀條件標記(markup)而變得混亂,考慮將子元件抽取出來,讓程式碼更乾淨。在 React 中,標記(markup)是程式碼的一部分,因此你可以使用變數和函式等工具來整理複雜的運算式。

邏輯 AND 運算子 (&&)

另一個常見的簡寫是 JavaScript 的邏輯 AND (&&) 運算子。

在 React 元件中,當你希望在條件為真時渲染某些 JSX,否則什麼也不渲染時,經常會用到 &&。利用 &&,你可以僅在 isPackedtrue 時有條件地渲染勾選符號:

return (
  <li className='item'>
    {name} {isPacked && "✅"}
  </li>
);

這段程式碼可以解讀為:「如果 isPacked 為真,那麼 (&&) 渲染勾選符號;否則,什麼都不渲染。」

以下是完整範例:

function Item({ name, isPacked }) {
  return (
    <li className='item'>
      {name} {isPacked && "✅"}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

JavaScript 的 && 運算式 在條件為真時,會回傳右邊的值(在這裡是勾選符號)。但如果條件為假,整個運算式就會回傳 false。React 將 false 視為 JSX 樹中的「空位」,就像 nullundefined 一樣,不會在其位置上渲染任何內容。

💡 注意事項 - 不要將數字放在 && 的左側在測試條件時,JavaScript 會自動將左側的值轉換為布林值。然而,如果左側的值是 0,整個運算式就會回傳該值(0),而 React 會很樂意直接渲染 0,而不是什麼都不渲染。例如,一個常見的錯誤是寫成 messageCount && <p>New messages</p>。可能會以為當 messageCount0 時不會渲染任何內容,但實際上它會渲染 0 本身!要修正這個問題,可以讓左側成為布林值:messageCount > 0 && <p>New messages</p>

條件式將 JSX 指派給變數

當語法捷徑讓程式碼變得不易閱讀時,可以嘗試使用 if 語句和變數來實現條件渲染。使用 let 定義的變數可以重新指派,因此可以先指定預設要顯示的內容,例如名稱:

let itemContent = name;

接著使用 if 語句,在 isPackedtrue 時,重新指派 JSX 表達式給 itemContent

if (isPacked) {
  itemContent = name + " ✅";
}

大括號打開了「進入 JavaScript 的窗口」。 在回傳的 JSX 樹中,用大括號嵌入變數,將先前計算出的表達式嵌入 JSX 中:

<li className='item'>{itemContent}</li>

這種風格是最冗長、最囉唆的,但同時也是最靈活的。以下是範例實作:

function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = name + " ✅";
  }
  return <li className='item'>{itemContent}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

如同之前的範例,這種方法不僅適用於文字,也適用於任意 JSX 表達式:

function Item({ name, isPacked }) {
  let itemContent = name;
  if (isPacked) {
    itemContent = <del>{name + " ✅"}</del>;
  }
  return <li className='item'>{itemContent}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}
截圖

(官網範例截圖)

如果你對 JavaScript 不太熟悉,這些多樣的寫法一開始可能會讓你覺得有點難以消化。然而,學會這些寫法將有助於你閱讀和撰寫任何 JavaScript 程式碼,而不僅僅是 React 元件!你可以先選擇你偏好的方式開始,然後在忘記其他方式時,再回來參考這篇教學。

💡 筆者補充 - 使用 const 的條件式渲染也許有人會好奇,這裡除了 let,也可以使用 const 嗎?答案是:可以。但如果使用 const,則不能直接重新賦值(reassign),因此需要用不同的方式來處理條件。例如,直接用條件運算符(三元運算子)來決定值,然後賦值給 const。以下是一個簡單的範例:- 使用 const itemContent,透過條件運算符直接在一行中決定 itemContent 的內容。

  • 如果 isPackedtrue,則 itemContent 為帶 <del> 的 JSX;如果是 false,則直接顯示 name。```jsx function Item({ name, isPacked }) { const itemContent = isPacked ? {name + " ✅"} : name;

    return

  • {itemContent}
  • ;

}

export default function PackingList() { return (

Sally Ride's Packing List

); } ```讀者應該會發現這種寫法,與前面介紹的「條件(三元)運算子 (? :)」的寫法是類似的,只是先賦值給 const,再回傳 const 的值。

總結

  • 在 React 中,你可以使用 JavaScript 控制分支邏輯。
  • 你可以透過 if 語句有條件地回傳 JSX 表達式。
  • 你可以將某些 JSX 有條件地儲存到變數中,然後使用大括號將其嵌入其他 JSX 中。
  • 在 JSX 中,{cond ? <A /> : <B />} 的意思是 「如果 cond 成立,渲染 <A />,否則渲染 <B />
  • 在 JSX 中,{cond && <A />} 的意思是 「如果 cond 成立,渲染 <A />,否則什麼都不渲染」
  • 這些簡寫方式很常見,但如果你偏好使用一般的 if,也完全沒有問題。

試試看一些挑戰 (Try out some challenges)

建議搭配官網提供的小視窗,可以直接在網頁上修改程式碼,並觀察結果。

挑戰 1 of 3: 使用 ? : 為未完成項目顯示圖示

使用條件運算子(cond ? a : b)在 isPackedfalse 時渲染一個 ❌。

function Item({ name, isPacked }) {
  return (
    <li className='item'>
      {name} {isPacked && "✅"}
    </li>
  );
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item isPacked={true} name='Space suit' />
        <Item isPacked={true} name='Helmet with a golden leaf' />
        <Item isPacked={false} name='Photo of Tam' />
      </ul>
    </section>
  );
}

挑戰 2/3:使用 && 顯示項目的重要性

在這個範例中,每個 Item 元件會接收一個以數字表示(numerical)的 importance 屬性。使用 && 運算子,僅在 importance 不為零時渲染「(Importance: X)」斜體文字。最後你的項目清單應該如下:

  • Space suit (Importance: 9)
  • Helmet with a golden leaf
  • Photo of Tam (Importance: 6)

別忘了在兩個標籤之間加上一個空格!

function Item({ name, importance }) {
  return <li className='item'>{name}</li>;
}

export default function PackingList() {
  return (
    <section>
      <h1>Sally Ride's Packing List</h1>
      <ul>
        <Item importance={9} name='Space suit' />
        <Item importance={0} name='Helmet with a golden leaf' />
        <Item importance={6} name='Photo of Tam' />
      </ul>
    </section>
  );
}

挑戰 3/3:將一系列的 ? : 重構為 if 和變數

這個 Drink 元件使用了一系列的 ? : 條件,根據 name 屬性是 "tea" 還是 "coffee" 來顯示不同的資訊。

問題在於,每種飲品的資訊分散在多個條件中。請重構此程式碼,使用單一的 if 陳述式來取代三個 ? : 條件。

function Drink({ name }) {
  return (
    <section>
      <h1>{name}</h1>
      <dl>
        <dt>Part of plant</dt>
        <dd>{name === "tea" ? "leaf" : "bean"}</dd>
        <dt>Caffeine content</dt>
        <dd>{name === "tea" ? "15–70 mg/cup" : "80–185 mg/cup"}</dd>
        <dt>Age</dt>
        <dd>{name === "tea" ? "4,000+ years" : "1,000+ years"}</dd>
      </dl>
    </section>
  );
}

export default function DrinkList() {
  return (
    <div>
      <Drink name='tea' />
      <Drink name='coffee' />
    </div>
  );
}

一旦你將程式碼重構為使用 if,你有沒有進一步簡化它的想法?

結語

這篇文章介紹了在 React 中進行條件式渲染的幾種方法。你學到了如何使用 if 語句、? : 運算子和 && 運算子來根據條件渲染不同的 JSX。你還學到了如何使用變數來儲存 JSX,以及如何使用 if 陳述式來重構複雜的條件式。

對於 React 開發者來說,這些技巧是很重要的,因為你經常需要根據應用程式的狀態來動態渲染 UI。

本文參考資料

https://react.dev/learn/conditional-rendering

liz_avatar

Liz

软件工程师

喜欢冷笑话和搜集迷因。有点社交焦虑。在 MBTI 中连续多年的测试结果为 ISTP。最爱的活动是坐在沙发上和好朋友玩电动一整天。

查看作者的其他文章

分享到

回上页