iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0

學習完 state 構築的原則,來進一步看 state 的應用,今天要學習如何在元件間共用 state 來達到更精準的控制權責關係。我們會透過一個手風琴(Accordion)的例子來練習。

這段程式碼呈現了一個可展開的面板效果。它包括兩個主要元件:Panel 和 Accordion。每個 Panel 組件代表一個可展開的面板,具有標題和內容。Accordion 元件是主要的容器,包含了兩個 Panel 子組件,它們代表了不同主題的內容。當用戶點擊「Show」按鈕時,對應的面板會展開,顯示相應的內容。

import { useState } from "react";

function Panel({ title, children }) {
  const [isActive, setIsActive] = useState(false);
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>Show</button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About">
        With a population of about 2 million, Almaty is Kazakhstan's largest
        city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology">
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for
        "apple" and is often translated as "full of apples". In fact, the region
        surrounding Almaty is thought to be the ancestral home of the apple, and
        the wild <i lang="la">Malus sieversii</i> is considered a likely
        candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

這段程式碼透過共用的 state 和 Panel 元件,讓使用者能在二個面板操作時都能顯示對應的畫面,而二個面板皆為彼此獨立的。

若是我們要做更近一步的設定,「打開A面板的時候B面板要顯示關閉,打開B面板的時候,A要顯示關閉」的話,該如何操作呢?

我們要用到名為「lift their state up」的操作,這個操作有以下三個步驟:

  1. 將狀態從子組件中移除
  2. 透過共同的父元件中傳遞硬編碼的數據( hardcoded data )
  3. 在共同的父元件中添加 state 並將 event handler 一併傳遞

1. 將狀態從子組件移除

斷開 Panel 元件和isActive 的連結,先將這行移除

const [isActive, setIsActive] = useState(false);

isActive作為 props 傳遞到 Panel 元件當中。

function Panel({ title, children, isActive })

2. 透過共同的父元件中傳遞硬編碼的數據( hardcoded data )

像這樣Panel的父元件為AccordionisActive 作為硬編碼來傳遞到每個Panel元件:

import { useState } from "react";

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About" isActive={true}>
        With a population of about 2 million, Almaty is Kazakhstan's largest
        city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology" isActive={true}>
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for
        "apple" and is often translated as "full of apples". In fact, the region
        surrounding Almaty is thought to be the ancestral home of the apple, and
        the wild <i lang="la">Malus sieversii</i> is considered a likely
        candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>Show</button>
      )}
    </section>
  );
}

硬編碼指的是程式碼中直接指定固定數據或值,而不是從外部來源或設定中動態獲取。這些固定的數據或值通常直接寫入程式碼,並且在程式運行時不會變化。譬如像是在這個程式碼當中,硬編碼指的是titleisActive

3. 在共同的父元件中添加 state 並將事件處理程序一併傳遞

首先在父元件 Accordion 當中追加狀態變數的設定,這次我們不用 true 和 false,而是採用 0 和 1 來處理,這邊要留意的是 0 或 1 不代表開或者關,而是代表了面板的索引編號。

const [activeIndex, setActiveIndex] = useState(0);

使用者按下按鈕時則會觸發activeIndex的設定,第一個 ActiveIndex 為 0,第二個 ActiveIndex 則是 1。當第一個面板處於活躍(展開)狀態時,activeIndex 將是 0;當第二個面板處於活躍(展開)狀態時,activeIndex 將是 1。也就是說每個 activeIndex 的值都對應到一個特定的面板。

import { useState } from "react";

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}>
        With a population of about 2 million, Almaty is Kazakhstan's largest
        city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}>
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for
        "apple" and is often translated as "full of apples". In fact, the region
        surrounding Almaty is thought to be the ancestral home of the apple, and
        the wild <i lang="la">Malus sieversii</i> is considered a likely
        candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive, onShow }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? <p>{children}</p> : <button onClick={onShow}>Show</button>}
    </section>
  );
}

透過 isActive 的真偽值來決定手風琴的開合是正確的。isActive 是一個布林值,它決定了面板是否處於活躍(展開)狀態。由於兩個面板共用相同的 activeIndex 狀態變數,所以當其中一個按鈕被點擊時,會改變 activeIndex 的值,進而影響 isActive 的真偽狀態,決定了面板的開合狀態。這樣的設計實現了手風琴效果,當一個面板打開時,另一個面板將自動關閉。

小結

今天學習的內容主要的一個核心概念是「誰在控制」,譬如我們將狀態變數移至父元件這件事,本身就是在釐清一個權責關係。透過交付這樣子的控制權,而達到了「切換」功能的動態性 accordion。

參考資料

  • React 官方文件:Sharing State Between Components

上一篇
Day 19 - 構築 state 的原則
下一篇
Day 21 - 保存和重置 state
系列文
30 days of React 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言