iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 5

Day 05|State 跟 Props 到底差在哪?這份資料到底是誰的?

  • 分享至 

  • xImage
  •  

上一篇我們把備忘錄搬進 React,開始看到:

const [memos, setMemos] = useState<string[]>([]);

也知道大致流程是:

使用者操作
↓
更新 State
↓
React Render
↓
UI 跟著改變

但開始看真正的 React 專案後,很快又會看到另一種東西:

<ProductList products={products} />

這時候我以前很容易混在一起:

State?
Props?
反正都是資料?

後來才慢慢理解,真正要先問的其實不是:

「它叫 State 還是 Props?」

而是:

這份資料是誰在管理?又是從哪裡來的?


State:這個 Component 自己管理的資料

先從最簡單的例子開始:

function MemoApp() {
  const [text, setText] = useState("");

  return (
    <input
      value={text}
      onChange={e => setText(e.target.value)}
    />
  );
}

這裡:

text

就是 State。

可以先理解成:

這個 Component 自己需要記住,而且資料改變後會影響畫面的內容。

流程:

使用者輸入
↓
onChange
↓
setText()
↓
text 更新
↓
React Render
↓
input 顯示新的內容

所以:

const [text, setText] = useState("");

可以先拆成:

text
→ 現在的資料

setText
→ 更新這份資料的方法

Props:別的 Component 傳進來的資料

假設現在頁面越來越大。

我們不想把所有東西都塞在:

MemoApp

裡面。

所以把輸入區拆成另一個 Component:

function MemoInput() {
  return ...
}

但問題來了。

原本的 text 在:

MemoApp

裡。

MemoInput 要怎麼知道現在輸入的是什麼?

可以從 Parent 傳下去:

function MemoApp() {
  const [text, setText] = useState("");

  return (
    <MemoInput text={text} />
  );
}

然後 Child 接:

type MemoInputProps = {
  text: string;
};

function MemoInput({ text }: MemoInputProps) {
  return (
    <input value={text} />
  );
}

這時候:

text

MemoInput 來說,就是 Props。

資料流可以畫成:

MemoApp

State
text = "買牛奶"

↓ Props

MemoInput

text = "買牛奶"

↓ Render

<input value="買牛奶" />

所以可以先簡單記:

State
→ Component 自己管理的資料

Props
→ Parent 傳進來的資料

那 Child 想改資料怎麼辦?

這裡就是我以前最容易卡住的地方。

假設:

text

其實是 Parent 的 State。

那 Child 收到後,可以直接改嗎?

例如:

function MemoInput({ text }: MemoInputProps) {
  text = "新的內容";
}

不是這樣處理。

Props 對 Child 來說,可以先理解成:

這是 Parent 傳給我的資料,我不直接改它。

如果 Child 希望資料改變,可以把「通知 Parent 的方法」也一起傳下來。

例如 Parent:

function MemoApp() {
  const [text, setText] = useState("");

  return (
    <MemoInput
      text={text}
      onTextChange={setText}
    />
  );
}

Child:

type MemoInputProps = {
  text: string;
  onTextChange: (value: string) => void;
};

function MemoInput({
  text,
  onTextChange,
}: MemoInputProps) {
  return (
    <input
      value={text}
      onChange={e =>
        onTextChange(e.target.value)
      }
    />
  );
}

現在流程就變成:

Parent
│
│ State: text
│
├──── text ─────────────┐
│                      ↓
│                    Child
│                      │
│                 使用者輸入
│                      │
│                      ↓
│              onTextChange()
│                      │
└──────────────────────┘
          ↓
       setText()
          ↓
      State 更新
          ↓
      React Render

這也是 React 裡很常見的資料流:

資料往下傳,事件往上通知。


為什麼不要每個 Component 都自己存一份?

假設 Parent 有:

const [text, setText] = useState("買牛奶");

Child 又自己建立:

const [text, setText] = useState("買牛奶");

看起來好像沒問題。

但其實現在是兩份不同的資料:

Parent text
→ "買牛奶"

Child text
→ "買牛奶"

一開始剛好一樣。

但如果 Parent 變成:

"買咖啡"

Child 自己那份 State 並不會因為名字一樣,就自動跟著變。

這時候就會開始出現一個很麻煩的問題:

到底哪一份才是真的?

這其實又回到前一篇提到的:

Source of Truth 在哪裡?

如果多個 Component 都要使用同一份資料,就要先想清楚:

誰負責管理?
↓
誰只是使用?

而不是每個地方都自己複製一份 State。


真實專案裡,拆 Component 後這個問題會突然變明顯

這也是我後來在實際專案裡,才真正開始理解 Props 的地方。

假設一開始整個頁面都寫在同一個 Component:

DetailPage
│
├─ 基本資料
├─ 商品資料
├─ 文件資料
└─ 其他資訊

因為全部都在一起,所以其實不太會感覺到「資料怎麼傳」。

但開始拆 Component 後:

DetailPage
│
├─ BasicInfo
├─ ProductInfo
├─ DocumentInfo
└─ OtherInfo

問題馬上出現:

BasicInfo 需要哪些資料?

ProductInfo 需要哪些資料?

DocumentInfo 又要哪些資料?

這時候就不能只是把 JSX 剪出去。

還要開始整理:

資料在哪裡?
↓
誰擁有?
↓
要傳給誰?

例如:

<ProductInfo
  products={detail.products}
  currency={detail.currency}
/>

光看 Props,就可以開始知道:

ProductInfo 依賴:

products
currency

所以拆 Component 之後,資料依賴反而會變得比較清楚。


State 跟 Props 可以先這樣分

我現在比較喜歡這樣記:

State Props
資料從哪來 Component 自己管理 Parent 傳進來
誰負責更新 擁有 State 的 Component 通常由 Parent 決定
Child 能直接改嗎 用自己的 setter 更新 不直接修改 Props
改變後 可能重新 Render Parent 傳入新值後重新 Render

如果再縮短:

State
→ 我管理的資料

Props
→ 別人傳給我的資料

但比起背這兩句,更重要的是看到一份資料時開始問:

這份資料在哪裡建立?
↓
誰負責管理?
↓
誰需要使用?
↓
誰可以修改?
↓
修改後要通知誰?

今天把資料流再往前走一步

前幾天我們一路在問:

資料在哪?
↓
資料怎麼讓 UI 改變?

今天再多一層:

資料是誰的?
↓
誰負責管理?
↓
哪些 Component 要使用?

所以 Component 之間的關係,可以先記成:

Parent
│
│ State
│
↓
Props
│
↓
Child
│
│ Callback / Event
│
↑
通知 Parent

當這條線開始看得懂之後,很多原本很亂的 React 程式碼,就可以先問:

這個 Component 是資料的主人,還是只是使用別人傳進來的資料?

下一篇就接著進一個我工作後很常遇到的問題:

一個頁面到底什麼時候該拆 Component?拆得越多真的越好嗎?


上一篇
Day 04|到了 React,為什麼不直接改畫面?
下一篇
Day 06|Component 不是拆越多越好:一個頁面到底什麼時候該拆?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言