--- title: 卡比 React 讀書會 | Tip calculator tags: React, 讀書會 --- # 卡比 React 讀書會 | Tip calculator | week 2 ## Tip calculator - [設計稿連結 - 請自行下載!](https://www.frontendmentor.io/challenges/tip-calculator-app-ugJNGbJUX) - [卡比直播連結](https://youtu.be/oVmLNvF-C5o) - Source code 自己去找卡比的 Github 找到算你贏XD ### 使用技術、工具 - React - Tailwindcss - Vite - [Vitawind](https://vitawind-blog.vercel.app/) - [clsx](https://www.npmjs.com/package/clsx) ### Vitawind 推薦使用兔兔大神製作好的 cli 方便快速~ - [Vitawind](https://vitawind-blog.vercel.app/scaffolding/) ### Tailwind 小提醒 - `sr-only` Tailwind 幫你隱藏,可用在 Radio button 的 `input` 上面 (雖然你要隱藏他,但他還是需要存在的時候可以用) - 善用 `peer` 可以快速使用 CSS 來達到 Radio button 的效果,下面範例展示了,當 `input` 被 `checked` 的時候可以讓後面的 `label` 改變字體顏色,需要注意的是 `peer-{}` 必須要再 `peer` 後面並同一層才能觸發。 ```jsx= function RadioButton (){ return ( <div> <input className="peer"/> <label className="peer-checked:text-green-500"> Label </label> </div> ) } ``` ### React 小提醒 1. 可以使用 `<>` 當作 fragment 來包裝要 return 出去的結構 ```jsx= function Component(){ return ( <> <label /> <input /> </> ) } ``` 2. component 可以加上 children 的 prop 就可以從外層加入到該元件的指定位置 ```jsx= `./component.jsx` function Component({ children }){ return ( <div class="bg-white rounded-lg"> {children} </div> ) } `./App.jsx` import Component from './component.jsx' function App(){ return ( <main> <Component> // ... 放你的東東 </Component> </main> ) } ``` 3. cloneElement 從父層丟元件進去子層時,子層可以來處理共用的 props ```jsx= `./component.jsx` // function Component({ children }){ // return ( // <div class="bg-white rounded-lg"> // // ... // cloneElement // </div> // ) // } `./App.jsx` // import Component from './component.jsx' // function App(){ // return ( // <main> // <Component /> // </main> // ) // } ``` 4. Form 處理 - `onChangeCapture` 綁在 form 上面,當 form 裡面有任何改動,就可以從父層直接觸發指定 method - `onResetCapture` 用來處理 form reset 時要觸發的 method - 小提醒 `requestAnimationFrame` "可能" 會在這邊用到,用來處理使函式觸發時間延後 ```jsx= function Form(){ function onChangeCapture(e){ const form = Object.fromEntries( new FormData(e.currentTarget).entries() ); console.log(form) } return ( <form onChangeCapture={onChangeCapture}> <input type="text" /> <input type="text" /> </form> ) } ``` ### Step 1 | 預先設定各種前置作業 1. Tailwindcss theme.colors 設定 2. 這邊選用在 CSS (`style.css`) 設定 body 背景色 3. SVG - 小提醒,SVG 有以下的調整才方便使用程式控制 - 有 `viewbox` - `fill` 改成 `currentcolor` - `width`, `height` 拿掉 - 使用了 [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr) 來方便 import SVG ### Step 2 | 版面處理 && 邏輯處理 首先,去看影片啦! 我不會寫筆記QQ - 分析版面 - 雖然他沒有 submit,但還是要使用 `<form>` - 記得使用 `<header>`、`<main>` - input 一樣要有 `outline` or `border` or `ring`,才符合 accessibility 規範喔~ - 善用 `button[type=reset]` 就不用特別處理了~ - 本日重點 - RadioGroup - 關鍵,這邊卡比把全部的按鈕都做成 radio button(包含 custom 欄位),再使用 `custom` 作為 `prop` 丟進去,來判斷是否額外產生 `input[type=number]` 並隱藏 radio button,如此一來包含 custom 的欄位就都會有 radio 的控制,不需要特別判斷 custom 欄位與 radio 的交互作用。 - 記得要用 `<fieldset>` - 使用 `label` 的 `for` 來對應 `input` 的 `id`,就可以在點擊 `label` 時也會觸發 `input` 的點擊 ```jsx= function RadioGroup({ id, label, children }){ return ( <fieldset> <legend>Label</legend> <div> {Children.map(children, (child) => cloneElement(child, {name: id}))} </div> </fieldset> ) } function Radio({ label, value, name, custom }){ return ( <div> <input type="radio" id={value} name={name} value={value} /> <lable htmlFor={value} className="clsx('...', custom && 'sr-only')" /> <!-- 用 ref 作為 prop 拋子層,React 會出警告,這邊先略過,直接重製一個 input[type=number] --> { custom && ( <input type="number" /> ) } </div> ) } function App(){ return( <div> <RadioGroup id="tips" label="Select Tip %" /> <Radio label="5%" value="5" /> <Radio label="10%" value="10" /> <Radio label="custom" value="custom" custom /> <RadioGroup /> </div> ) } ```