今天要解釋的是: 如何將下層State提升給上層元件。
但如果沒有舉例真的太抽象了,所以就乾脆做個功能,邊進行解釋吧。
功能包含:
實作步驟:
我們先製作一個Product的元件:
// components/product.js
import React, { useState } from "react";
const Product = () => {
const product = {
name: "Apple", // 商品名稱
price: 100, // 價格
}
return(
<div>
<h2>{product.name}</h2>
<p>${product.price}</p>
<h3>quantity:</h3>
<div>
<button >+</button>
<button >-</button>
</div>
<hr/>
</div>
)
};
export default Product
然後引入到App.js進行渲染
// app.js
import React from "react";
import Product from "./components/product";
import "./styles/styles.css";
const App = () => {
return(
<div>
<Product/>
</div>
)
};
export default App
目前會是一個有【商品名稱】【價格】【數量】【增減按鈕】的清單
但是現在只有渲染畫面,按按鈕清單也不為所動,所以要開始幫他加上一些功能:
增加數量
預設的商品數量是0,所以這時候可以幫【數量】設useState(0)
:
const [ quantity, setQuantity] = useState(0); // quantity 預設值= 0
...
<h3>quantity:{quantity}</h3> //渲染至畫面
可以看到現在的【數量】是 0:
接著先幫"+"按鈕做增加數量的功能,宣告一個increment
的函式:
const increment = () => {
setQuantity(quantity + 1); // 可以想成 quantity = quantity + 1
};
然後要記得幫button綁上功能:
<button onClick={increment}>+</button> // 點擊時執行increment(); quantity + 1
可以按按看"+",會增加數量(被我按到5了):
減少數量
宣告decrement
函式,在數量>0時,執行quantity - 1
:
const decrement = () => {
if ( quantity > 0 ){
setQuantity(quantity - 1); // quantity = quantity - 1
}
};
因為商品不能是負數,所以這裡簡單加了一個if的判斷式。
然後幫button綁上decrement
的功能:
<button onClick={decrement}>-</button>
到這裡應該可以成功減少數量了:
接著,我們要讓更多商品應用同樣的功能,所以要使用剛剛完成的Product元件。
架構可以想像成這樣:
App和Component有上下級關係,
App裡面包住了幾個Product元件,在App設定好的資料可以作為參數(Prop)傳入元件(Component)。
概念上來說,component 就像是 JavaScript 的 function,它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。
(如果是規模更大的專案,應該不會直接把參數綁到App,可能會先做頁面包住元件。
但這裡是示範功能,就把架構作的簡化了一點。)
先在App設定好商品資訊,傳入component:
// app.js
import React from "react";
import Product from "./components/product";
import "./styles/styles.css";
const App = () => {
// 設定商品資訊
const products = [
{id: 1, name: "Android", price: 150},
{id: 2, name: "Apple", price: 170},
{id: 3, name: "Nokia", price: 65},
];
return(
<div>
// 將商品資訊作為參數(Prop)分別傳入元件(Component)
{products.map( (p) => (
<Product
key={p.id}
name={p.name}
price={p.price}
/>
))}
</div>
)
};
export default App
然後記得到product.js接住傳入的props:
// components/product.js
import React, { useState } from "react";
const Product = ({key, name, price}) => { // 用{大括號}接受prop
...
return(
<div>
<h2>{name}</h2> // 把product.name改成name
<p>${price}</p> // 把product.price改成price
<h3>quantity:{quantity}</h3>
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
<hr/>
</div>
)
};
export default Product
到這裡可以試試看購物清單的功能:
應該可以成功的顯示商品和增減數量了!
最後也是最重要的,計算總價。
假設我們要製作另一個元件Total顯示總價,直覺上的架構可能會長這樣:
但這個結構會產生一個問題: TotalCach
要怎麼知道每個元件的state的變化?
通常來說,有一些 component 需要反映相同的資料變化。我們建議將共享的 state 提升到最靠近它們的共同 ancestor。
所以可能要改變一下結構, 可以直接把totalCash提升為元件App的State,
同時把totalCash作為props傳入元件Total。
// app.js
const [ totalCash, setTotalCash ] = useState(0); // totalCash預設值為0
const calculate = (price) => {
setTotalCash( totalCash + price); // totalCash = totalCash + price
}
總價的預設值為0,總價隨著價格增減,後面會解釋得比較仔細。
calculate(price)
會被作為prop傳入component。// app.js
{products.map( (p) => (
<Product
key={p.id}
name={p.name}
price={p.price}
onCalculate={calculate} //
/>
))}
calculate(price)
的參數price
就是被傳入product component的propprice={p.price}
。
// product.js
const Product = ({key, name, price, onCalculate}) => { // 記得加入onCalculate
const [ quantity, setQuantity] = useState(0);
const increment = () => {
setQuantity(quantity + 1);
onCalculate(price); //每次點擊時執行 onCalculate(price) = totalCash + price
};
const decrement = () => {
if ( quantity > 0 ){
setQuantity(quantity - 1);
onCalculate(-price); // 減少時為-price
}
};
// total.js
import React, { useState } from "react";
const Total = ({totalCash}) => {
return(
<div>
<h2>Total:{totalCash}</h2>
</div>
)
};
export default Total
app.js要加入這一行
// app.js
<Total totalCash={totalCash}/>
最後,執行看看清單的功能:
可以成功進行增減了!!
最後附上Glitch完整程式碼
(Glitch打開來可能要等有點久:(
【如內文有誤還請不吝指教>< 並感謝閱覽至此的各位:D 】
參考資料