---
# System prepended metadata

title: 卡比 React 讀書會 | Calculator App
tags: [React, ' 讀書會']

---

---
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)

![](https://i.imgur.com/MeHrBSN.png)

- 核心概念: 由一個初始 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 計算機很適合拿來放在作品集，因為有一定的複雜度