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 專案後,很快又會遇到另一個問題:

<Component data={data} />

這個 data 又是什麼?

有時候資料放在 useState

const [data, setData] = useState([]);

有時候卻從 Component 上面傳進來:

<List data={data} />

以前我很容易把它們全部當成:

「反正都是資料。」

但真正開始拆 Component 後,我才慢慢理解:

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 顯示新的 text

所以:

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

可以拆成:

text
→ 現在的資料

setText
→ 更新這份資料的方法

Props:別的 Component 傳進來的資料

接下來假設頁面越來越大。

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

MemoApp

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

function MemoInput() {
  return ...
}

但問題來了。

原本的 text 在:

MemoApp

裡面。

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

這時候就可以從 Parent Component 傳下去:

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

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

MemoInput 再接:

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

這裡的:

text

MemoInput 來說,就是 Props

資料流變成:

MemoApp

State
text = "買牛奶"

↓ Props

MemoInput

text = "買牛奶"

↓ Render

<input value="買牛奶" />

所以可以先簡單記:

State
→ Component 自己管理的資料

Props
→ Parent Component 傳進來的資料

那子 Component 想修改資料怎麼辦?

這裡就是我以前最容易混亂的地方。

既然:

text

是 Parent 的 State:

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

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

例如:

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

不應該這樣做。

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

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

現在資料流就完整了。

MemoApp
│
│ State: text
│
├──── text ────────────┐
│                     ↓
│                 MemoInput
│                     │
│                使用者輸入
│                     │
│                     ↓
│               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 都需要同一份資料,通常會先思考:

這份資料應該由哪一層負責管理?

而不是每個 Component 都複製一份自己的 State。


實際專案裡,拆 Component 後這個問題會變得很明顯

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

假設原本整個頁面都寫在同一個 Component:

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

所有資料都在同一個地方時,其實不用特別思考「資料怎麼傳」。

因為大家都在同一個 Component 裡。

但當頁面開始拆成:

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

問題馬上出現:

BasicInfo 需要哪些資料?

ProductInfo 需要哪些資料?

DocumentInfo 又需要哪些資料?

這時候就不能只是:

「把程式碼剪出去就好了。」

還要開始整理資料流。

例如:

DetailPage
↓
取得 API Response
↓
保存需要的資料
↓
透過 Props
├─ BasicInfo
├─ ProductInfo
└─ DocumentInfo

Component 拆分之後,我反而會更容易看到:

每個 Component 到底依賴哪些資料?

這也是 Props 對我來說開始真正有意義的時候。


State 跟 Props 可以先這樣分

先不用背太正式的定義。

我目前比較喜歡這樣理解:

State Props
資料從哪來 Component 自己管理 Parent 傳進來
誰負責更新 擁有 State 的 Component Parent
Child 可以直接修改嗎 使用 setter 更新自己的 State 不直接修改 Props
改變後 可能觸發重新 Render Parent 傳入新 Props 後 Child 重新 Render

如果再濃縮一點:

State
→ 我管理的資料

Props
→ 別人傳給我的資料

但真正重要的不是背這兩句。

而是看到一份資料時開始問:

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

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

前幾天我們一路從:

資料在哪?

走到了:

資料怎麼讓 UI 改變?

今天又多了一層:

資料由誰管理?
↓
哪些 Component 需要它?
↓
要透過 Props 傳給誰?

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

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

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

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

下一篇就可以正式進到一個我實際工作後很常遇到的問題:

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


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

尚未有邦友留言

立即登入留言