3-7 子層元件如何修改父層元件的資料狀態

本單元對應的專案分支為:change-props-in-parent


在前一個單元中,我們已經知道怎麼把資料的狀態當成 props 傳入子層元件中,但除了資料狀態之外,函式或用來改變資料狀態的方法也可以透過 props 傳入。

先前為了避免一次帶入太多觀念,還沒有把 <div className="converter">...</div> 拆成一個獨立的元件,現在就讓我們來看一下可以怎麼做,還有實作上會碰到什麼問題吧!

UnitConverter 元件的拆分#

和 UnitControl 元件一樣,我們先在 components 資料夾中新增一支名為 UnitConverter.js 的檔案,內容就把 <div className="converter">...</div> 中的內容給剪進來:

// ./src/components/UnitConverter.js
import React from 'react';
const UnitConverter = () => <div className="converter">{/* ... */}</div>;
export default UnitConverter;

接著同樣在 App.js 中把 UnitConverter 這個元件匯入:

// ./src/App.js
import UnitConverter from './components/UnitConverter';
// ...
function App() {
// ...
return (
{/* ... */}
<div className="card-body">
<UnitControl />
<UnitConverter />
</div>
{/* ... */}
);
}
export default App;

這時候如果讀者透過 npm start 把專案啟動的話,會看到這樣的錯誤訊息:

Imgur

這個錯誤訊息的意思是說,在 UnitConverter 這個元件中,找不到 handleInputChangeinputValue 這個兩變數。

透過 props 傳遞需要的資料與函式#

Imgur

這是因為在 UnitConverter 中有使用到 inputValuehandleInputChange 這兩個變數,但這兩個變數原本是放在 App 中,還沒有透過 props 傳進去,這時我們只需要用上一個單元中所學的,透過 props 把這兩個變數從 App 傳入到 UnitConverter 就可以了,像是這樣:

// ./src/App.js
const handleInputChange = (e) => {
const { value } = e.target;
setInputValue(value);
};
// ...
return (
{/*...*/}
<UnitConverter
inputValue={inputValue}
handleInputChange={handleInputChange}
/>
)

這裡你可以留意到,在 JavaScript 中函式本身就和物件一樣,因此 handleInputChange 一樣可以透過 props 傳到子層元件中。

回到子層的 UnitConverter 元件中,接著這裡一樣透過參數的方式就可以取得父層元件 props 傳進來的內容,接著在透過物件的解構賦值把需要的資料或函式取出即可:

// ./src/components/UnitConverter
const UnitConverter = (props) => {
const { handleInputChange, inputValue } = props;
return /* ... */;
};

同樣的,也可以直接在函式參數的地方就直接做解構賦值的動作,同時因為 UnitConverter 這個元件並不需要做其他事情,可以直接回傳 JSX,因此回傳的內容可以直接放在箭頭函式的 => 後面,就不需要再加上大括號 { },像是這樣:

const UnitConverter = ({ handleInputChange, inputValue }) => (
<div className="converter">{/* ... */}</div>
);

到這裡我們就完成了 UnitConverter 元件的拆分。

重要:子層元件不可直接修改父層元件傳入的 props#

這裡要特別說明一個很重要的觀念,不論是在 CardFooter 或 UnitConverter 元件中,我們都有透過 props 取得父層 App 元件中的 inputValue 來使用。在 React 元件間的資料傳遞中有一個非常重要的概念,就是「只有該資料的擁有者可以去修改資料」,什麼意思呢?

以這裡來說,你可以看到 inputValue 是在 App 中被建立,它是該資料的擁有者,雖然透過 props 可以把 inputValue 的值傳遞到 UnitConverter 和 CardFooter 中,但這兩個子層元件只能讀取 inputValue 的值,它們是沒有權限去修改 inputValue 的。

Imgur

雖然子層不能直接修改父層的 props,但是它可以請父層幫他完成這個資料修改的動作。實際上來說,就是先把修改資料的函式在父層定義好,像是 App 元件中的 handleInputChange 這個修改 inputValue 的方法,接著一樣透過 props 把這個方法傳到子層內。

現在當子層需要修改父層的資料狀態時,就只需要呼叫 handleInputChange 這個方法,即可修改父層傳入的 props。

換你了:動手拆分 UnitConverter 元件吧#

現在換你動手來把 UnitConverter 拆成一個獨立的元件了,可以參考下面的步驟:

  • components 資料夾中新增 UnitConverter.js 的檔案
  • 把原本在 App<div className="converter">...</div> 的部分剪下到 UnitConverter
  • 將 App 元件中的 handleChangeinputValue 透過 props 傳遞到子層元間
  • 在 UnitConverter 元件中透過函式的參數 props 取得 App 傳入資料和函式

本單元相關之網頁連結、完整程式碼與程式碼變更部分可於 change-props-in-parent 分支檢視:https://github.com/pjchender/learn-react-from-hooks-internet-speed-converter/tree/change-props-in-parent

Imgur