iT邦幫忙

2021 iThome 鐵人賽

DAY 22
0
自我挑戰組

登堂入室!前端工程師的觀念技術 _30_ 題系列 第 22

21. React簡易實作_購物車清單( 將下層State提升給上層元件 )

今天要解釋的是: 如何將下層State提升給上層元件

但如果沒有舉例真的太抽象了,所以就乾脆做個功能,邊進行解釋吧。

實作一個購物清單功能為例


功能包含:

  • 增減商品數量
  • 計算總價

實作步驟:

  1. 製作元件(Component)
  2. 增減商品數量(useState)
  3. 使用元件(應用Prop)
  4. 計算總價(提升State)

1. 製作元件

我們先製作一個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

目前會是一個有【商品名稱】【價格】【數量】【增減按鈕】的清單
https://ithelp.ithome.com.tw/upload/images/20210922/20129476OxVIVv4cTN.png

但是現在只有渲染畫面,按按鈕清單也不為所動,所以要開始幫他加上一些功能:

2. 增減商品數量(useState)


增加數量

預設的商品數量是0,所以這時候可以幫【數量】設useState(0):

const [ quantity, setQuantity] = useState(0);  //  quantity 預設值= 0
        ...
      <h3>quantity:{quantity}</h3>  //渲染至畫面

可以看到現在的【數量】是 0:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476zQeDGcxIK4.png

接著先幫"+"按鈕做增加數量的功能,宣告一個increment的函式:

	const increment = () => {
		setQuantity(quantity + 1);   // 可以想成 quantity = quantity + 1
	};

然後要記得幫button綁上功能:

    <button onClick={increment}>+</button>  // 點擊時執行increment(); quantity + 1

可以按按看"+",會增加數量(被我按到5了):
https://ithelp.ithome.com.tw/upload/images/20210922/20129476NgFnusdgih.png


減少數量

宣告decrement函式,在數量>0時,執行quantity - 1:

  const decrement = () => {
    if ( quantity > 0 ){
		  setQuantity(quantity - 1);    // quantity = quantity - 1
    }
  };

因為商品不能是負數,所以這裡簡單加了一個if的判斷式。

然後幫button綁上decrement的功能:

    <button onClick={decrement}>-</button>

到這裡應該可以成功減少數量了:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476ulD0wsbUNG.png

3. 使用元件(應用Prop)


接著,我們要讓更多商品應用同樣的功能,所以要使用剛剛完成的Product元件。

架構可以想像成這樣:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476XdgAuGS1wd.png
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

到這裡可以試試看購物清單的功能:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476v5bDo7yiCy.png

應該可以成功的顯示商品和增減數量了!

4. 計算總價(提升State)


最後也是最重要的,計算總價。

假設我們要製作另一個元件Total顯示總價,直覺上的架構可能會長這樣:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476xdLfCLInu2.png

  • App 有兩個 child components: Total和Product。
  • totalCash計算總價,配合Product的State變化

但這個結構會產生一個問題: TotalCach要怎麼知道每個元件的state的變化?

通常來說,有一些 component 需要反映相同的資料變化。我們建議將共享的 state 提升到最靠近它們的共同 ancestor。

所以可能要改變一下結構, 可以直接把totalCash提升為元件App的State,
同時把totalCash作為props傳入元件Total。

https://ithelp.ithome.com.tw/upload/images/20210922/20129476pfcbwwJg0S.png

每當兩個或以上的子元件資料會互相影響,就可以透過提升State的方式,將State提升到ancestor(上級元件),再透過傳入prop的方式控制資料。

  1. 在App裡設置State:
// app.js
  const [ totalCash, setTotalCash ] = useState(0);  // totalCash預設值為0
  	
  const calculate = (price) => {
    setTotalCash( totalCash + price);   // totalCash =  totalCash + price
  }

總價的預設值為0,總價隨著價格增減,後面會解釋得比較仔細。

  1. 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}

  1. 在增加或減少商品數量時,執行onCalculate():
// 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
    }
  };
  1. 製作Total元件,傳入totalCash作為prop。
// 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}/>

最後,執行看看清單的功能:
https://ithelp.ithome.com.tw/upload/images/20210922/20129476uVM6EiZEtu.png

可以成功進行增減了!!

最後附上Glitch完整程式碼
(Glitch打開來可能要等有點久:(

【如內文有誤還請不吝指教>< 並感謝閱覽至此的各位:D 】

參考資料


上一篇
20. React Hooks 想改善的問題 ( + 簡單實作 useState)
下一篇
22. React Hooks --- useEffect
系列文
登堂入室!前端工程師的觀念技術 _30_ 題31

尚未有邦友留言

立即登入留言