---
title: 卡比 React 讀書會 | Calculator App
tags: React, 讀書會
---
# 卡比 React 讀書會 | Calculator App | week 3
[Youtube](https://youtu.be/4woOH2ECDXU)
[FrontendMentor](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa2JRQVZpcGo3N1IwU0w3eE0xNFZ5Mm1NNEh0QXxBQ3Jtc0tuNGN1TDAtNThVSkpHUXNNLVJELVc1UTRzNkVFeHNnY1pydUhCWjBuYXY4YlhkTVptYjFGeExEdnVBZXcxMFhHVnQyTzUzc241cU0tSFdFal9meDJ2dFV3aldjdVFPeHM4YmdQYXgyUkNkNTlTRzdBcw&q=https%3A%2F%2Fwww.frontendmentor.io%2Fchallenges%2Fcalculator-app-9lteq5N29)
[Template](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbHFxWWtwUjM1Y2F6VkVvdHYxcm9pVXZtN1FWZ3xBQ3Jtc0tscFZBa0JaczZNeWUwZlR4X1h0QnM5dWhCallIaEpqRDZhQjZxNnM4Y3E5YVZ5T05sMmU1S2FVanZfcno1VHhFeEdkdTVWVjJCQzZKRXFWSEJTdFk0TWNmRHdzdFRYMVhoVElTUVEySmh4cGhuRlFIaw&q=https%3A%2F%2Fgithub.com%2FRabbittee%2Fhello-react-calculator-app)
[Final](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa0V6ZGI1cXFsUVRSOWl1RExqcGZnOGUyWmpPQXxBQ3Jtc0tud1lmUnpMbGhtbENxcWRfLTQ2eHdNM2dqdFRYdE40UFdDakhpVGNjS3hoSHZrY3RnRlZsWml2UnZiTzhQWXlMel9tTUh3aHpTdm1yMlZZa2NGZ2xMaDVyWVdkazNlVWNvanZLellmLUFld25RVWRfaw&q=https%3A%2F%2Fgithub.com%2Fkayac-chang%2Fhelloxreact-calculator-app)
[Demo](https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqa01fcWg0Tm9mcjI0SllqLXRqWWU0Q3R0OXpyUXxBQ3Jtc0ttQk9GSnBfUm9NdkVJc2ZwTFBiR18wVWJ3MVl0MHVkMmo1LXFnd042YXRkU0hDSkFZcE83X2pDNXZmOWt6WGVEaTZIVFR3TzR1WEVTRFltRDhlTXR4X1RsdzNtX2k3M0VmLWVpZ2ZGdTNZcTVGTEVkZw&q=https%3A%2F%2Fhelloxreact-calculator-app.vercel.app%2F)
- 小提醒,Template 的部分已經有先處理好 Tailwind config、Font,可以直接快速使用!
- 使用了 [tailwindcss-multi-theme](https://github.com/estevanmaito/tailwindcss-multi-theme)
## 前言
再次提醒 [Semantics](https://developer.mozilla.org/en-US/docs/Glossary/Semantics) 的重要性,只要有操作行為,就適合放到 `<form>` 裡面不限定於表單。
計算機邏輯今天都會跳過,這次著重於 Hooks 喔!
## Theme Toggle
- 實作方式是在 body 加上指定的 theme class 從而影響子元素裡面的樣式。
- `useEffect` - 需要注意的是在 function component 重新渲染時會反覆執行暴露在外面的 methods,但我們不見得每次都想要重複觸發,此時就是適合使用 `useEffect` 的時候了。當元件被渲染的同時會觸發 side effect,換句話說,當 `useEffect` 關注的參數改變時,就會執行裡面的事件。
- `onChangeCapture` - 這邊需要注意的是,可以使用 `onChangeCapture` 來綁定在父元素上面,藉此優化效能。
*這邊跳過 class,不然內容過長*
```jsx=
const theme =[
{
label: "1",
value: 'theme-blue',
},
{
label: "2",
value: 'theme-yellow',
},
{
label: "3",
value: 'theme-gray',
},
]
function ThemeToggle(){
const [active, setActive] = useState("theme-blue")
useEffect(() => {
themes.forEach({value} => {
document.body.classList.toggle(value, value === active)
})
// 這邊的 Array 裡面的值有變動的時候,這個 useEffect 裡面的事件才會被執行
}, [active])
const index = themes.findIndex(({value})=> value === active);
return (
<div
role="group"
aria-labelledby="theme"
onChangeCapture={(event) => setActive(event.target.value)}
>
<span>Theme</span>
<!-- switch -->
<div>
<div>
{theme.map(({label, value}) => (
<label key={value} htmlFor={value}>
{label}
</label>
))}
</div>
<div>
{theme.map(({value}) => (
<input key={value} type="radio" id={value} name="theme" />
))}
</div>
</div>
</div>
)
}
```
> 語意小教室
> 卡比: 上一堂課有說到 Radio button 需要使用 `fieldset`、`legend` 等等的標籤來實作以符合標準,盡可能可以達到上述做法比較好,但有時候使用 `div` 是可以讓樣式客製化更有彈性,但可讀性就會差一點,以上需要自行斟酌。
```jsx=
function SemanticsDiv(){
return (
<div role="group" aria-labelledby="theme">
<span id="theme">Theme</span>
</div>
)
}
function SemanticsFieldset(){
return (
<fieldset>
<legend>Theme</legend>
</fieldset>
)
}
```
## Calculator
- [`<output>`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/output),用來顯示運算結果,他還可以自己運算,不過這邊是直接給它結果來渲染。
- [composition vs 繼承](https://zh-hant.reactjs.org/docs/composition-vs-inheritance.html),卡比展示了一個小技巧,`Button`、`Number`、`Del`、`Reset` 附用了同一組的來自 `Button` 裡面的 `className`,但 `Number`、`Del`、`Reset` 又分別有各自的 `className` (甚至 `Reset` 還附用了 `Del` 的 `className` ),與此同時把所有的 `props` 透過解構傳遞到 `Button` 上面處理。
```jsx=
function Button( {className, children} ){
return (
<button
type="button"
className={clsx("shadow", className)}
>
{children}
</button>
)
}
function Number( {props} ){
return (
<Button
type="button"
className={clsx("text-red-500")}
{...props}
/>
)
}
function Del( {props} ){
return (
<Button
type="button"
className={clsx("text-green-500")}
{...props}
/>
)
}
function Reset( {props} ){
return (
<Del
type="button"
className={clsx("col-span-2")}
{...props}
/>
)
}
```
## 簡述 Hooks
新手可以直接使用 React Hooks,相較於 Class 來說更為容易理解並且平坦化因此較容易維護,比起 Class 來說更接近 Functional Programming。
[卡比說給你聽](https://youtu.be/4woOH2ECDXU?t=4186)
### 客制化自己的 Hook
可以把各種邏輯另外包裝成客制化的 Hook,這樣可以把邏輯切出來,使用時就不需要考慮裡面的邏輯,方便重用。
[React Doc](https://zh-hant.reactjs.org/docs/hooks-custom.html)
```jsx=
// * Custom Hook - AAA
function AAA(){
const [state, setState] = useState()
// custom...
return []
}
// * Main Application
function App(){
// 可以從這邊使用 AAA()
const [] = useAAA()
}
```
### useReducer
類似於 [Redux](https://redux.js.org/tutorials/essentials/part-1-overview-concepts)

- 核心概念: 由一個初始 State,用來渲染畫面,透過使用者操作畫面時,發出 action 來改變 State 而觸發畫面的改變,從而反覆循環的遞迴。
- 目的: 讓你可以透過此規則來追蹤資料,以及追蹤資料是透過甚麼 action 來改變。
- 小細節:
- 每次 return 出去的 state 都要是 **新的**,這樣才可以前後比較
- 盡量在裡面處理好 action 為一個物件,從外面使用只要呼叫對應的 action 名稱,而不會暴露邏輯在外面
*下面只有寫 reset & push*
```jsx=
// ./hooks.jsx
import {useReducer, useMemo} from 'react'
function reducer(state, action){
if(action.type === 'RESET'){
// 如果 action 是 `RESET` 就在這邊改變狀態
}
if(action.type === 'PUSH'){
// 如果 action 是 `PUSH` 就在這邊改變狀態
}
return state
}
export function useCalculator(){
const initialState = ["0"]
// `state` => 當前狀態
// `dispatch` => 用來發送 action 的函式
// `reducer` => 你想使用的 reducer
// `initialState` => 用來設定初始狀態,必須要在外面寫,不可以寫在 reducer 裡面!
const [state, dispatch] = useReducer(reducer, initialState)
// useMemo 用來避免每次執行的時候重複產生裡面的東西,在這裡的情況則是由於後面陣列裡面的 dispatch 的參考不會變動,所以每一次都會使用同一組的內容
// 需注意 dispatch 裡面 "通常" 發送 action 時會用物件的形式,這是約定俗成的規定,也可以不這麼做
// 這裡會回傳物件,其包含 actions 讓你可以從外面來直接呼叫 action
const actions = useMemo(
() => ({
reset: () => () => dispatch({ type: "RESET" }),
push: () => () => dispatch({ type: "PUSH" }),
}),
[dispatch]
)
// compute 這邊處理結果
const operator = findLastOperator(state)
const last = findLast(state)
const output = isOperator(last) ? state[state.length - 2] : last;
return {operator, output, actions}
}
// ./app.jsx
import React from 'react'
import { useCalculator } from './hooks.jsx'
function App(){
const {operator, output, actions} = useCalculator();
return (
<Keyboard operator={operator} actions={actions}/>
)
}
function Keyboard({
operator,
actions: { reset, push, remove, operate, enter }
}){
function Number(props){
return (
<Button
onClick={push(props.children)}
{...props}
/>
)
}
function Reset(props){
return (
<Button
onClick={reset()}
{...props}
/>
)
}
return (
<Number>1</Number>
<Number>2</Number>
<Reset>Reset</Reset>
)
}
function Button(props){
return (
<button onClick={onClick}></button>
)
}
```
## 計算機邏輯
請直接觀看影片,我不會寫成筆記 orz
## 卡比後記
後面的讀書會回到簡單一點的程度,這周比較難一點不過是來帶過常用的 hooks,所請不要灰心
BTW 計算機很適合拿來放在作品集,因為有一定的複雜度