在目前的前端生態中,TypeScript 與 React 已成為前端工程師的主要工具之一,使用 TypeScript 能確保我們專案的更加的完善(可讀性高、錯誤降低)和 React 提供彈性的組件架構,讓大家能夠快速建構。結合這兩個語言的幫忙,相信能創造出來很棒的專案作品~不過在這之前我還是先來繼續把基礎打好,接下來我們將著眼於一個零售業者模擬演練的前端呈現,透過這個例子。預計能在表單控制、數據和前端呈現方面有更進一步吧。
首先,我們使用 npx create
來建立一個新的 React 專案。接著,為了畫出圖表,我們將安裝 react-chartjs-2
和 chart.js
。
npx create-react-app retail-simulation
npm install react-chartjs-2 chart.js
定義了一個 SaleRecord
型別,這將用於定義每週的銷售記錄。(X軸)
// types.ts
export type SaleRecord = {
week: number;
remainingStock: number;
};
使用 react-chartjs-2
套件來建立一個線圖。這個套件接收每組 SaleRecord
數據,並將其轉換程圖所需的格式。另外在使用的過程中!!!必須註冊 Chart.js 的組件。為了確保能夠正常使用所有的圖表功能。當然用一種確引入所有圖表有點猛,所以後續有機會再來介紹每一種圖片。
// components/LineChart.tsx
import React from 'react';
import { Line } from 'react-chartjs-2';
import { SaleRecord } from './types';
import { Chart, registerables } from 'chart.js';
type Props = {
data: SaleRecord[];
};
const LineChart: React.FC<Props> = ({ data }) => {
Chart.register(...registerables);
const chartData = {
labels: data.map(record => `Week ${record.week}`),
datasets: [
{
label: 'Remaining Stock',
data: data.map(record => record.remainingStock),
fill: false,
backgroundColor: 'rgb(75, 192, 192)',
borderColor: 'rgba(75, 192, 192, 0.2)',
},
],
};
return <Line data={chartData} />;
};
export default LineChart;
Chart.register(...registerables);
:這一行可以進行 Chart
的圖表初始化,並透過 registerables
來讓所有圖都先載入,相當的方便,一開始沒想到還得初始化XD,想說為何總是出現型態錯誤,害我搞混了。React.FC<Props>
:當說到 React.FC
,其實就是 Function Component 的縮寫。在 React 中,有兩種主要的方式來定義組件:1. 類組件(Class Component)和 2. 函數組件(Function Component)。隨著 React Hooks 的出現,函數組件的功能變得更加強大(畢竟是函式功能導向,很符合函式程式的主旨,因此它們現在被廣泛地使用。) 所以也能寫成**React.FunctionComponent
**。當使用它來定義組件時,能確保組件接受 props
且返回正確的 JSX 元素,還提供了自動完成和型別檢查,這在開發過程中非常有幫助。chartData
的部分就是為了畫一個折線圖,去控制資料內的部分( SaleRecord 內的 week 和 remainingStock ),每週的數值,其他就是一些常見圖表參數。未來在詳細說明。<Line data={chartData} />;
// components/PricingControl.tsx
import React from 'react';
type Props = {
onSelect: (discount: number) => void;
};
const PricingControl: React.FC<Props> = ({ onSelect }) => {
const discounts = [100, 90, 80, 70, 50];
return (
<div>
{discounts.map((discount) => (
<button key={discount} onClick={() => onSelect(discount)}>
{discount}% of the price
</button>
))}
</div>
);
};
export default PricingControl;
暫時透過點選的方式來控制每週的商品價值(販售價格,預設從1000元開始折價,畢竟東西應該會越來越便宜XD)。
Props
:一樣有一個負責傳遞資訊,主要傳遞點選的數字,在進行折扣的處理。const discounts = [100, 90, 80, 70, 50];
:定義常數,當作有100%~50%的這週販售價格。discounts
陣列中的每一個折扣,都創建一個按鈕,當按鈕被點擊時,它會調用 onSelect
函數,並將當前的 discount
值作為參數傳遞。// logic/salesLogic.ts
export const simulateSales = (stock: number, discount: number): number => {
let sold = Math.floor(stock * (1 - discount / 100) * (Math.random() * 0.1 + 0.9));
return stock - sold;
};
主要當作最簡單的銷售邏輯基本上就是普通的亂數隨機。
(1 - discount / 100)
: 計算打折後的價格。Math.random() * 0.1 + 0.9
: 這部分假裝模擬了,基於隨機性的銷售變動。它生成一個在0.9到1之間的隨機數字,而實際販售則會稍微變成這會由隨機值之類的處理。stock
: 基於當前存貨和計算出的售價以及隨機變動,計算預期的售出數量。當然這一塊我還不知道多少就是了。Math.floor()
: 使用 Math.floor
確保銷售數量是整數。// App.tsx
import React, { useState } from 'react';
import LineChart from './Component/LineChart';
import PricingControl from './Component/PricingControl';
import { SaleRecord } from './Component/types';
import { simulateSales } from './Component/salesLogic';
const INITIAL_STOCK = 1000;
const App: React.FC = () => {
const [salesData, setSalesData] = useState<SaleRecord[]>([
{ week: 1, remainingStock: INITIAL_STOCK },
]);
const handlePricingSelect = (discount: number) => {
const currentWeek = salesData.length;
const previousStock = salesData[currentWeek - 1].remainingStock;
const newStock = simulateSales(previousStock, discount);
setSalesData([...salesData, { week: currentWeek + 1, remainingStock: newStock }]);
};
return (
<div className="App">
<h1>Retail Simulation</h1>
<PricingControl onSelect={handlePricingSelect} />
<LineChart data={salesData} />
</div>
);
};
export default App;
import
:想當然先引入各自所需的程式碼,包含自己撰寫的部分。const INITIAL_STOCK = 1000;
:先隨便預設基底。useState Hook
:透過 hook 來掌控我們目前的銷售數據,也就是之前定義的 SaleRecord
。const handlePricingSelect
:定義用來控制我們點選折扣的時候,來模擬該週的銷售結果跟更新結果。成果大概就如下。