--- 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 計算機很適合拿來放在作品集,因為有一定的複雜度