3-6 React 元件間的資料傳遞 - props 的使用

本單元對應的專案分支為:pass-props-to-components


在上一個單元中已經拆分出了兩個獨立的 React 元件 - UnitControlCardFooter。現在我們想要讓 CardFooter 能夠根據使用者輸入的網速快慢來顯示不同的文字內容和顏色樣式。

在上一章中,我們已經學過如何讓 JSX 能夠根據不同的資料狀態來呈現不同的內容,所以條件轉譯和動態切換 CSS 樣式的方式其實你應該已經知道了。比較不一樣的地方是,現在使用者輸入的網速 inputValue 這個狀態是保存在 App 這個元件中,而 CardFooter 是一個獨立的元件,CardFooter 並沒有辦法直接知道 App 中 inputValue 的值是多少,必須要把這個值從 App 傳遞到 CardFooter 後,CardFooter 才會知道 inputValue 的值。因此這裡我們就需要先來了解如何在 React 各元件之間進行的資料傳遞。

透過 props 在元件間傳遞資料狀態#

先讓我們用一張圖來描述當前網速單位換算器的結構:

Imgur

從上圖中可以看到,在專案中現在一共有三個 React 元件,其中最上層的 App 裡面會包含 inputValue 這個狀態(state)和用來改變這個狀態的 setInputValue 這個方法。

接著在 App 這個元件中還匯入並使用了 UnitControlCardFooter 這兩個元件,在這樣的關係中,我們會說 App 是父層元件(parent component),UnitControlCardFooter 是子層元件(child component)。

小提醒

父層和子層是相對的概念,例如這裡 UnitControl 是 App 的子層元件,但若在 UnitControl 中還有匯入並使用另一個元件,這時候 UnitControl 則會是另一個元件的父層元件。

從這張圖中我們可以看到,因為 inputValue 的狀態是保存在 App 中,因此 CardFooter 並沒有辦法直接知道 inputValue 的值。在 React 中,子層元件如果想要得到父層元件的資料狀態,只需要透過 props 的方式來傳送資料就可以了:

Imgur

講解完了概念之後,讓我們來看具體要怎麼做。

父層透過 props 傳遞資料#

父層要傳遞 props 到子層的方式非常簡單,假設現在我們有名為 ChildComponent 的子層元件,想要把父層元件中的 firstNamelastName 這兩個資料傳遞進 ChildComponent 中,只需要透過像是 HTML 屬性的方式傳進去就可以了:

// 父層元件
// STEP 1: 將資料透過 html 屬性的方式傳入 component 內
const ParentComponent = () => (
<ChildComponent firstName="Aaron" lastName="Chen" />
);

子層元件接收 props 資料的方式#

接著子層元件 ChildComponent 只需要透過「函式參數」的方式來接收父層元件傳進來的 firstName 和 lastName 即可。這裡我們透過在函式參數中帶入 props 這個參數,即可取得父層傳進來的資料,透過 props.firstNameprops.lastName 就可取得對應的值:

// 子層元件
// STEP 2: 在該 component 內可以透過參數 props 取得傳入的資料
const ChildComponent = (props) => {
return (
<h1>
Hello, {props.firstName} {props.lastName}
</h1>
); // Hello, Aaron Chen
};
小提醒

這裡慣例上會把函式參數的名稱稱作 props 但實際上名稱是可以自由命名的。

在取用 props 的時候,會習慣使用解構賦值直接把需要的變數取出來,因此在取用 props 的地方會像這樣寫:

// 透過解構賦值把 props 內需要用到的變數取出
function ChildComponent(props) {
const { firstName, lastName } = props;
return (
<h1>
Hello, {firstName} {lastName}
</h1>
); // Hello, Aaron Chen
}

甚至更精簡到連 props 都不命名了,直接在參數中透過解構賦值取出來用:

// 透過解構賦值直接在「函式參數的地方」把需要用到的變數取出
function ChildComponent({ firstName, lastName }) {
return (
<h1>
Hello, {firstName} {lastName}
</h1>
); // Hello, Aaron Chen
}

上面這些都是很常見的寫法。

將 inputValue 傳遞到 CardFooter 中使用#

回到網速單位換算器,現在想要把父層元件 App 的資料 inputValue 透過 props 傳到子層的 CardFooter 元件中,只需要透過像是 HTML 屬性的方式傳進去就可以了。

透過 props 將 inputValue 從 App 傳入 CardFooter#

我們可以在 <App /> 元件中使用 <CardFooter /> 的地方,把想要傳入的資料透過 <CardFooter key={value} /> 的方式傳入,在 value 的地方把想要傳遞到 CardFooter 的資料值帶入,也就是 inputValue;在 key 的地方,方便起見我們一樣取名為 inputValue,但這並不是一定的,名稱同樣可以自行命名,而這個 key 會用在 CardFooter 接收 props 時使用:

// ./src/App.js
function App() {
//...
return (
<div className="container">
{/* ... */}
{/* STEP 1: 把想要傳入 CardFooter 的資料透過 key={value} 的方式傳入 */}
<CardFooter inputValue={inputValue} />
</div>
);
}
小提醒

key 的命名主要是讓子層元件取用 props 時使用,並沒有硬性規定要用什麼名稱,只是這裡剛好都取做 inputValue

在 CardFooter 從 props 取得 App 傳進來的 InputValue#

接著在 <CardFooter /> 的元件中,就可以在參數中透過 props 取得傳進來的資料,props 本身會是一個物件,因此一樣可以透過解構賦值的方式,把想要的資料取出:

// STEP 2:透過 props 取得從父層傳入的資料
const CardFooter = (props) => {
const { inputValue } = props;
// ...
};

整個流程會像這樣:

Imgur

如果你對於 props 還不是這麼熟悉的話,也可以在 <CardFooter /> 中透過 console.log(props) 把它呈現出來看一下。

換你了:在 CardFooter 取得 App 的 inputValue#

現在輪到你練習把 App 中的狀態 inputValue 透過 props 傳遞到 CardFooter 中,實做的流程會像是這樣:

  • 在 App 中透過 html 標籤的方式把 inputValue 傳到 CardFooter 元件
  • 在 CardFooter 中,透過函式參數的方式,取得 App 傳進來的 props
  • 在 CardFooter 中透過 console.log(props) 確認有得到 props

根據 inputValue 改變 CardFooter 的樣式#

現在我們已經可以在 CardFooter 取得 App 中的資料狀態,最後要來根據這個 inputValue 搭配前一章說明的條件轉譯和動態 CSS 樣式來讓 CardFooter 可以動態改變。

這裡的邏輯會是這樣:

  • inputValue 沒有輸入時,Footer 會顯示 ---,顏色會是 #d3d8e2
  • inputValue 小於 15 時,Footer 會顯示 SLOW,顏色會是 #ee362d
  • 15 <= inputValue < 40,Footer 會顯示 GOOD,顏色會是 #1b82f1
  • inputValue 大於等於 40 時,Footer 會顯示 FAST,顏色會是 #13d569

這裡我們可以使用 if...else...else if 做出類似的判斷:

// ...
const CardFooter = (props) => {
const { inputValue } = props;
let criteria = {};
if (!inputValue) {
criteria = {
title: '---',
backgroundColor: '#d3d8e2',
};
} else if (inputValue < 15) {
criteria = {
title: 'SLOW',
backgroundColor: '#ee362d',
};
} else if (inputValue < 40) {
// ...
} else if (inputValue >= 40) {
// ...
}
// ...
};

接著在最後 return JSX 的地方,把對應的背景顏色和標題帶進去就可以了:

return (
<div
className="card-footer"
style={{
backgroundColor: criteria.backgroundColor,
}}
>
{criteria.title}
</div>
);

完成後,當使用者輸入的數字不同時,CardFooter 就會對應出現不同的文字內容和顏色:

Imgur

換你了:根據 inputValue 改變 CardFooter 的文字內容和樣式#

在已經取得 App 中的 inputValue 後,要請你根據 inputValue 來讓 CardFooter 顯示不同的內容和背景樣式,類似的流程如下:

  • 建立判斷 inputValue 來顯示不同內容和背景樣式的邏輯
  • 將判斷後的結果以變數和 style 的方式帶入 JSX 中

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

Imgur