有時候你的 component 會需要根據不同的條件來顯示不同的內容。在 React 中,你可以使用 JavaScript 中 if 陳述式、&& 以及 ? : 運算子等語法來根據條件 render 不同的 JSX。

You will learn

  • 如何根據不同條件回傳不同 JSX
  • 如何有條件地包含或排除一段 JSX
  • React codebases 中常見的條件式簡短語法

條件性地回傳 JSX

舉例來說,你在 PackingList component 中 render 了數個可以被標記為打包完成與否的 Item component:

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 component 的 isPacked prop 的值是 true 而非 false。而你想要在 isPacked={true} 的情況下,為已打包的項目加上一個勾號 (✔)。

你可以將此情境用以下 if/else 陳述式來表示:

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

如果 isPacked prop 的值為 true,這段程式碼會回傳一個不同的 JSX tree。藉由此更動,部分項目內容的最後會有一個勾號。

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 代表不回傳任何內容

在某些情況下,你可能不想 render 任何東西。舉例來說,你不想顯示任何已經打包好的項目,但 component 必須要有個回傳值。在這種情況下,你可以回傳 null

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

如果 isPacked 的值為 true,component 不回傳任何東西 (意即 null)。否則,component 會回傳要 render 的 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>
  );
}

在實際開發中,component 回傳結果為 null 可能並不在 render 該 component 的開發者的預期之內,所以此方法並不常使用。較為常見的方法是在 parent component 的 JSX 中,條件性地決定是包含或移除此 component。實作方法如下!

條件性地包含 JSX

在先前的範例中,你可以控制哪個 JSX tree 會被 component 回傳 (如果有的話!)。而你可能已經注意到 render 內容中有一些重複的部分:

<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,則 (?) render name + ' ✔';否則 (:),render name 」。

Deep Dive

上述兩個範例是完全等價的嗎?

如果你有物件導向開發的相關背景,你或許會因為其中一個範例有可能會建立兩個不同的 <li> instance,從而認為上面的兩個範例略有不同。但 JSX 元素其實並不是 instance,因為它們沒有保存任何內部狀態,也不是真正的 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>
  );
}

這種寫法適用於條件式簡單的情況,但要適度的使用。如果你的 component 因包含太多巢狀的條件標記而變得雜亂,應該要考慮提取出 child component 以進行程式碼整理。在 React 中,標記是你程式碼的一部分,所以你可以使用像是變數和函式這樣的工具來整理複雜的表達式。

邏輯 AND 運算子 (&&)

另一個常見的簡短語法是 JavaScript 邏輯 AND (&&) 運算子。在 React component 中,時常會遇到在條件成立時 render 一些 JSX,否則不呈現任何東西的情境。藉由使用 &&,你可以只有在 isPacked 的值為 true 時呈現勾號:

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

你可以把這段程式碼解讀為:「如果 isPacked 為真,就(?)render 勾號;否則(:),什麼都不 render 」。

實際用法如下:

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 && 運算子 會在左側的條件為 true 時回傳它右側的值(在範例中是勾號)。但如果條件為 false,整個表達式的結果就會變成 false。跟 nullundefined 一樣,React 會將 false 視為像是 JSX tree 中的一個 「洞」,因此不會 render 任何東西。

Pitfall

不要在 && 左側使用數值。

為了判定條件結果,JavaScript 會自動將左側轉換為布林值。但如果左側的值是 0,則整個表達式會取用它的值 (0),React 也因此會 render 0,而不是什麼都不 render。

例如,常見的一個錯誤是撰寫像 messageCount && <p>New messages</p> 這樣的程式碼。人們很容易就誤以為當 messageCount 的值為 0 時,它什麼都不會 render ,但實際上它會呈現出 0 本身!

要修正這個問題,可以將左側轉換為回傳布林值: messageCount > 0 && <p>New messages</p>

有條件地將 JSX 指定給變數

當使用簡短語法造成在寫程式碼本身有所不便時,你可以嘗試使用 if 陳述式和一個變數。使用 let 宣告的變數的值是允許你修改的, 所以可以先將你預設希望顯示的內容指定給這個變數,也就是 name:

let itemContent = name;

接著在 isPacked 的值為 true 時,使用 if 陳述式重新將 itemContent 的值指定為一個 JSX 表達式。

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

大括弧打開了引入 JavaScript 的大門。 由大括弧可以將變數嵌入回傳的 JSX tree 中,進而將先前計算的表達式巢狀地嵌在 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 component!挑一個你偏好的寫法作為起始,如果忘記了其他的寫法再回來參考即可。

Recap

  • 在 React 中,你透過 JavaScript 控制邏輯的分支。
  • 你可以使用 if 表達式有條件地回傳 JSX 表達式。
  • 你可以有條件地將一些 JSX 指定到一個變數,然後使用大括弧將它包起來以巢狀地嵌入其他 JSX 中。
  • 在 JSX 中, {cond ? <A /> : <B />} 表示 「如果 cond 為真,則 render <A />,否則 render <B />
  • 在 JSX 中, {cond && <A />} 表示 「如果 cond 為真,則 render <A />,否則什麼都不 render」
  • 簡短寫法很常見,但如果你偏好單純的 if 陳述式,完全可以不使用這些簡短寫法。

Challenge 1 of 3:
使用 ? : 為未打包完成的項目加上圖示

使用條件運算子(cond ? a : b)在 isPacked 的值不是 true 時 render 一個 ❌。

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>
  );
}