--- title: 卡比 React 讀書會 | Sign up form tags: React, 讀書會 --- # 卡比 React 讀書會 | Sign up form | week 1 ## Sign up form - [設計稿連結 - 請自行下載!](https://www.frontendmentor.io/challenges/intro-component-with-signup-form-5cf91bd49edda32581d28fd1) - [卡比直播連結](https://www.youtube.com/watch?v=IfaTpyhaGlg) - Source code 自己去找卡比的 Github 找到算你贏XD ### 使用技術 - React - Tailwindcss - Vite ### Step 1 - Tailwind config 設定 依照設計稿提供的 Style guide 配置,盡量放在 root 可以讓後續維護的人容易理解 - 主題色 - 字體 - 背景 #### `./tailwind.config.js` ```javascript= module.exports = { mode: "jit", purge: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], darkMode: false, // or 'media' or 'class' theme: { extend: { colors: { red: `#FF7979`, green: `#38CC8B`, blue: { DEFAULT: `#5E54A4`, dark: `#3D3B48`, }, gray: "#BAB7D4", }, boxShadow: { DEFAULT: `0px 8px 0px rgba(0, 0, 0, 0.14688)`, solid: `inset 0px -4px 0px rgba(0, 0, 0, 0.0908818)`, }, }, }, variants: { extend: {}, }, plugins: [], }; ``` #### `./src/style.css` ```css= @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap"); @tailwind base; @tailwind components; @tailwind utilities; html, body { font-family: "Poppins", sans-serif; @apply bg-red h-screen; } #root { @apply h-screen; background: url("./images/bg-intro-mobile.png"); } @media (min-width: 640px) { #root { background: url("./images/bg-intro-desktop.png"); } } ``` ### Step 2 - 開切 畫面切版 - 小叮嚀 - About Layout(讓我想想) 1. 盡量使用語意化的 HTML 標籤 (eg: `article`, `p`, `b`, `form`, `label`...etc),不要甚麼都用 `div` 2. 從手機版開始設計~ 3. CSS style 會像像瀑布一樣,樣式會往下一直跑,不需要每個標籤都設定,找到共同的父元素來設定即可。 - (CSS: Cascading Style Sheets)[https://developer.mozilla.org/en-US/docs/Web/CSS] 4. placeholder 讀字機讀不到,加上 label 的 form 才會比較符合無障礙 5. [Form 的相關規範](https://www.w3.org/WAI/tutorials/forms/) - About React 1. 元件可以保持沒有邏輯,有商業邏輯的部分拉到主要頁面上,方便後續管理 - 切元件 - 試著邊切版邊處理元件,不用回頭來處理 - 需要注意! - React 中的元件若有任何部分要更新,就會整個重新渲染,所以切得細一點將可以提升效能。 - 用你銳利的眼睛來觀察設計稿哪邊有共用的 style,不要重複做同樣的事情 - 共用元件用 `props` 來從父層傳入內容 - 使用某種方式 (請注意 `function clsx(){}` 的使用) 來增加元件的 style 彈性,這樣可以輕鬆的設定好預設的 class,並能在父元件使用元件時使用不同的 class 來製作不同的元件外觀 - ![](https://i.imgur.com/v34BZ74.png) - `useState` 狀態機 - 建議初始狀態不要用 `undefined` - eg: `const [_value, setValue] = useState("");` - ![](https://i.imgur.com/v2V32da.png) - 表單的 input 元件 ```jsx= function clsx(...str) { return str.filter(Boolean).join(" "); } function TextField({ id, label }) { const [value, setValue] = useState(""); return ( <div className="relative flex items-center"> <label htmlFor={id} className={clsx( "absolute px-3", "text-sm font-medium", value !== "" && "opacity-0" )} > {label} </label> <input type="text" name={id} id={id} className={clsx( "outline-none", "border border-opacity-10 w-full p-3 rounded", "border-blue-dark focus:border-blue" )} onChange={(event) => setValue(event.target.value)} /> </div> ); } ``` ### Step 3 Form's error state 1. Form submit 的時候取消預設的瀏覽器事件。 ```jsx= function onSubmit(event) { event.preventDefault(); } ``` 2. 使用 formData 來取得 Form 裡面的值 ```diff= function onSubmit(event) { event.preventDefault(); + const form = new FormData(event.target); + const data = Object.fromEntries(form.entries()); + console.log(data); } ``` 3. 跑迴圈來取得每一個 input 是否有值,並回傳 Boolean 值 ```diff= function onSubmit(event) { event.preventDefault(); const form = new FormData(event.target); - const data = Object.fromEntries(form.entries()); - console.log(data); + for (const [key, value] of form.entries()) { + const hasFilled = Boolean(value); + console.log(key, hasFilled); + } } ``` 4. 使用 Hook 來改變 FormState ```diff function onSubmit(event) { event.preventDefault(); const form = new FormData(event.target); const data = Object.fromEntries(form.entries()); - for (const [key, value] of form.entries()) { - const hasFilled = Boolean(value); - console.log(key, hasFilled); - } + setFormState((formstate) => + formstate.map((state) => ({ + ...state, + error: !Boolean(data[state.id]), + })) + ); } ``` 4. 處理錯誤時出現的 SVG - 先處理好 svg 檔 - 拿掉 svg 標籤的 width & height & fill 改成 currentColor,就可以用程式改變顏色與寬高 - SVG import 成一個元件 - [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr) 5. TADA! ```jsx= function TextField({ id, label, error }) { const [value, setValue] = useState(""); return ( <div> <div className={clsx("relative flex items-center", error && "text-red")}> <label htmlFor={id} className={clsx( "absolute px-3", "text-sm font-medium", value !== "" && "opacity-0" )} > {label} </label> <input type="text" name={id} id={id} className={clsx( "outline-none", "border w-full p-3 rounded", error ? "border-red" : "border-blue-dark focus:border-blue border-opacity-10" )} onChange={(event) => setValue(event.target.value)} /> {error && ( <ErrorIcon className="absolute right-0 py-3 mr-3 h-full w-6" /> )} </div> {error && ( <div className="flex justify-end"> <span className="text-xs text-red">{error}</span> </div> )} </div> ); } ``` ### Step 4 Deployment - 使用 Vercel,請看影片 1:55:44 左右 - [卡比部屬給你看](https://youtu.be/IfaTpyhaGlg?t=6944) > 波特媽: 要不要試試看vercel? 點點點擺著等等等就部署好了