Child to Parent Component Communication (Bottom-up)

在過去的課程中,我們可以透過 props 將資料由上而下從父元件傳遞到子元件,接下來將介紹如何層子元件由下而上傳遞資料到父元件

首先,我們先來了解一下為什麼會有這種由下而上傳遞資料的需求呢 ? 原因一般我們在開發時,會將頁面拆分成許多 Component 以利功能更加單純並兼具複用性,所以常會有資料從一個 Component 生成,但要反映到另一個 Component 來呈現,但傳遞資料只能以 Component 之間的關係垂直傳遞,無法任意傳遞到其他 Component,否則資料就會過於混亂難以管理。

那麼實作上到底該怎麼執行呢 ? 其實答案在之前的範例中已經出現了,那就是我們在監聽原生 input 時,透過 onChange 綁定一個 function,當 function 被執行時,其第一個參數就是從 input 回傳的 event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ExpenseForm.js
function ExpenseForm() {
const [enteredTitle, setEnteredTitle] = useState();

const titleChangeHandler = (event) => {
setEnteredTitle(event.target.value);
};

return (
<form>
...
<label>Title</label>
<input
type="text"
value={enteredTitle}
onChange={titleChangeHandler}
/>
...
</form>
);
}

回到我們的範例,首先,在父元件 (NewExpense) 綁定 event 到子元件 (ExpenseForm) 上,並期待它會回傳表單資訊 (expenseData),再透過 Math.random() 追加一組隨機產生的 unique id (可能會重複,僅供練習測試),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// NewExpense.js
const NewExpense = () => {
const enteredExpenseHandler = (expense) => {
const expenseData = {
...expense,
id: Math.random().toString(),
};
console.log('expenseData', expenseData);
};

return (
<div className='new-expense'>
<ExpenseForm onEnteredExpense={enteredExpenseHandler}/>
</div>
);
};

接著來到子元件,我們可以從 props 中取得傳進來的 event onEnteredExpense,並將表單資料 expenseData 回傳給它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ExpenseForm.js
function ExpenseForm(props) {
...
const submitHandler = (event) => {
event.preventDefault();

const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate),
};

props.onEnteredExpense(expenseData);
setEnteredTitle('');
setEnteredAmount('');
setEnteredDate('');
};

return (
...
);
}

但需要這份表單資料的另一組元件 ExpenseItem,所以我們還需要繼續往上傳至根元件 App.js,才能過它傳遞到目標元件,而 Component 垂直傳遞的方式無法跳過中間元件 NewExpense (後面有其他作法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// NewExpense.js
const NewExpense = (props) => {
const enteredExpenseHandler = (expense) => {
const expenseData = {
...expense,
id: Math.random().toString(),
};
props.onAddExpense(expenseData);
};

return (
<div className='new-expense'>
<ExpenseForm onEnteredExpense={enteredExpenseHandler}/>
</div>
);
};

// App.js
const App = () => {
...

const addExpenseHandler = expense => {
console.log('App.js', expense);
};

return (
<div className="App">
<NewExpense onAddExpense={addExpenseHandler}></NewExpense>
<Expenses items={expenses} />
</div>
);
}

資料參考

React - The Complete Guide (Incl Hooks, React Router, Redux)
Github