# React(MRWR)第 10 節: Custom Navigation and Routing System > Udemy課程:[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/) `20230911Mon.~20230921Thu.` :::danger 10-156 children prop 10-158 prop-types函式庫 10-159 Number()、truthy value、falsy value 10-160 Tailwind CSS([3-36 用Bulma](https://hackmd.io/yIqdj6sHS7OGPSN38Z_Miw?view#3-36-Review-on-how-CSS-Works)) 10-165 classnames函式庫 10-169 react-icons ::: ## 10-152. Project Overview 開始新專案之前,先來看看整個專案完成後的樣子: ![](https://hackmd.io/_uploads/rJp53BhR3.png) 左側為導覽行(navagation bar),右側則會依照導覽行的選擇,改變右側的元件顯示,例如該專案中我們點擊導覽行上不同的選項,分別可以看到下拉式選單、手風琴(accordion)選單、按鈕... 這個章節的目標如下圖: ![](https://hackmd.io/_uploads/BkB6aSnRn.png) 那建立一個新專案,一樣: ```javascript! npx create-react-app 10-comps ``` **** ## 10-153. Project Setup 那專案一開始的設置步驟,可以直接參考[第 3 節的內容](https://hackmd.io/@noz915/H1PJrHD5h)(懶得重打了) **** ## 10-154. Some Button Theory 這個章節要討論按鈕,有時後會發生同一個app,不同頁面下,會有不同的按鈕樣式: ![](https://hackmd.io/_uploads/SySOah0C3.png) > inconsistent 不一致的 ![](https://hackmd.io/_uploads/Bkjz0hRC2.png) 每位工程師都創建了不同的按鈕,那有沒什麼解決辦法,讓按鈕變得有一致性呢? PS 當然不只適用於按鈕,像是下拉式選單、輸入框等等都可以 樣式分為不同種,而每一種樣式有分為不同的功能(下圖為參考) ![](https://hackmd.io/_uploads/H1auxT00n.png) 像是下圖即為"ebay"的網頁按鈕,同個樣式,利用顏色區別不同功能的按鈕: ![](https://hackmd.io/_uploads/HkWRl60C3.png) **** ## 10-155. Underlying Elements 我們不會希望不同的樣式就創建一個放component的按鈕,這樣穢史整個資料變得難以閱讀(如下): ![](https://hackmd.io/_uploads/r1mlM6R0h.png) 所以我們製作的方法如下,利用prop來客製不同按鈕 ![](https://hackmd.io/_uploads/ByFuGaCR3.png) ### 首先,先建立一個檔案叫做`Button.js`,我們建立一個Button的component,而非直接使用`<button />`元素: **Button.js** ```javascript! function Button(){ return <button>Hi There !</button> } export default Button; ``` 接著要將Button元件import進`App.js`,然後我們新增好機個Button元件進去。 **App.js** ```javascript! import Button from "./Button"; function App(){ return ( <div> <div> <Button></Button> </div> <div> <Button></Button> </div> <div> <Button></Button> </div> <div> <Button></Button> </div> <div> <Button></Button> </div> </div> ) } export default App; ``` 現在往也應該會顯示五個Hi There !的按鈕: ![](https://hackmd.io/_uploads/Hk5wi6AA2.png) **** ## 10-156. The Children Prop 不過實作上,按鈕上的字不會直接打上去,因為每個按鈕要去顯示的內容都不盡相同,因此先把上一節打的Hi There !刪掉: **Button.js** ```javascript! function Button(){ return <button>Hi There !</button> } export default Button; ``` 那要怎麼顯示不同的字元呢?我們會運用到prop system來達成。 首先回到`App.js`,App component屬於Button component的父元件,所以我們要透過父元件將prop傳給子元件,因此我們建立一個prop物件,其中一個key名為text,並且我們先賦予該key一個value "Click Here !" > 可參考[第3章節的筆記](https://hackmd.io/yIqdj6sHS7OGPSN38Z_Miw#3-35-Handling-Image-Accessibility) **App.js** ```javascript! ... <div> <Button text="Click Here !"></Button> </div> ... ``` 接著回到`Button.js`,裡頭的Button 函式便可以接收這個text prop(記得可以裡用重構的方式傳入,忘記的話一樣可以回到第3章節複習),接收這個text prop作為參數之後,我就可以用來顯示在螢幕上: **Button.js** ```javascript= function Button({text}){ return <button>{text}</button> } export default Button; ``` 因為在`App.js`裡面,我目前只給一個Button元件加上prop,所以螢幕上現在的顯示應該只會有一個button會有內容而已 ![](https://hackmd.io/_uploads/rJMYX0RCn.png) ### 雖然上方的作法成功了,但是大多數的工程師都會希望Button元件,可以跟一般`<button><button />`元素一樣,在兩個tag之間打字就好。 ![](https://hackmd.io/_uploads/BkAALRAA2.png) 於是就誕生一種東西叫做"children prop",先回顧下原先使用text prop產生button內容的方式: ![](https://hackmd.io/_uploads/ryTP200Rh.png) 接著再看看如何運用"children prop"產生button內容: ![](https://hackmd.io/_uploads/HJO8hR0Rh.png) 現在我們把每個button都填上不同的內容 `App.js` ```javascript! import Button from "./Button"; function App(){ return ( <div> <div> <Button>Click Here !</Button> </div> <div> <Button>Buy Now !</Button> </div> <div> <Button>See Deal !</Button> </div> <div> <Button>Hide Ads !</Button> </div> <div> <Button>Something !</Button> </div> </div> ) } export default App; ``` **Button.js** ```javascript! function Button({children}){ return <button>{children}</button> } export default Button; ``` 螢幕上顯示的結果: ![](https://hackmd.io/_uploads/r1RKaA0A2.png) 而children prop不只可以用於文字,甚至也可以在傳入其他的prop作為children prop,只要在父元件兩個tag之間放入任何的內容,React都會自動地幫我們把該內容轉成children prop的value,並可以往下傳給子元件。 **** ## 10-157. Props Design 大致上會有以下的樣式 以variation來說,會有不同的規格,例如說Plain、Primary、Secondary......,若要呈現Primary,則利用prop system方式,把Primary設定為true,這些樣式都是利用布林值來顯示。 ![](https://hackmd.io/_uploads/H1auxT00n.png) 用布林值來顯示,需要注意Plain、Primary、Secondary......這些標準一次只能選擇一種為true,不能重複。 但是outline、rounder則可以同時顯示。 ![](https://hackmd.io/_uploads/S1GYz1yJT.png) 另外這裡提供一些縮短寫法的方式。 原先,我們若想讓button顯示為Primary,則可能會寫像下方: ```javascript! <Button primary={true}>Hi There !</Button> ``` 但是在JS中,我們可以直接寫primary即可,這樣就代表primary為true了: ```javascript! <Button primary>Hi There !</Button> ``` 若想用false,則可以直接不寫上primary(如下),不過事實上這裡不代表primary為false,而是表primary為undefined,但是效果會跟傳入false是一樣的: ```javascript! <Button>Hi There !</Button> ``` 以下是一些示範: **App.js** ```javascript! ... <div> <Button success rounded outline>Click Here !</Button> </div> <div> <Button danger outline>Buy Now !</Button> </div> <div> <Button warning>See Deal !</Button> </div> <div> <Button secondary outline>Hide Ads !</Button> </div> <div> <Button secondary rounded>Something !</Button> </div> ... ``` **** ## 10-158. Validating Props with PropTypes 這一節將介紹一項JS的技術(需要import進來),叫做"prop-types " > npm: [prop-types ](https://www.npmjs.com/package/prop-types) ![](https://hackmd.io/_uploads/ByvxDkkk6.png) 我們針對傳入Card元件的prop,利用propType設定一些規則: ![](https://hackmd.io/_uploads/HJ_0Py1k6.png) 1. title prop必須為一字串、該prop一定要被使用 ![](https://hackmd.io/_uploads/B1MVOykkp.png) 2. content prop必須唯一字串、但不一定要使用該prop ![](https://hackmd.io/_uploads/rJbgF1JJ6.png) ### 那要使用prop-types之前,記得要先在terminal中下載: ```javascript= npm install prop-types ``` ![](https://hackmd.io/_uploads/rJZQcJ1JT.png) **** ## 10-159. PropTypes in Action 上一章節已經利用npm下載好這個新套件了: ```javascript= npm install prop-types ``` 接著我們要在`Button.js`中使用,所以要記得先引入: **Button.js** ```javascript= import PropTypes from "prop-types"; ``` 這裡先複習一下,我們先前已在`App.js`中為其子元件Button加上了prop,這些prop看似沒有賦予key值,但事實上只寫上prop value,代表該value的key值為"true"。 **App.js** ```javascript! function App(){ return ( <div> <div> <Button success rounded outline>Click Here !</Button> </div> <div> <Button danger outline>Buy Now !</Button> </div> <div> <Button warning>See Deal !</Button> </div> <div> <Button secondary outline>Hide Ads !</Button> </div> <div> <Button secondary rounded>Something !</Button> </div> </div> ) } ``` 既然已從副元件傳入props了,那麼Button元件的函式參數勢必要接收這些prop進來。 **Button.js** ```javascript! function Button({ children, primary, secondary, success, warning, danger, outline, rounded }){ return <button>{children}</button> } ``` ![](https://hackmd.io/_uploads/HyzAFA1kp.png) 那PropTypes呢?我們先來了解為什麼要使用PropTypes。 從[10-158](https://hackmd.io/Q4WywDzmT0Sx6aiuFJ5t7A#10-158-Validating-Props-with-PropTypes)的例子: :::info 1. title prop必須為一字串、該prop一定要被使用 ![](https://hackmd.io/_uploads/B1MVOykkp.png) 2. content prop必須唯一字串、但不一定要使用該prop ![](https://hackmd.io/_uploads/rJbgF1JJ6.png) ::: 可知,PropTypes的作用類似於TypeScript,不過我們得先自己設定那些prop的type,如上面的第一個例子,我們設定title這個prop為string(字串)以及isRequired(在Card元件上一定得使用),而一旦開發者不小心把title這個prop 的key值賦予了字串以外的內容,程式將會報錯。 簡單來說,就是幫助我們「型別檢查」。 除此之外,我們可以從[prop-types的文件](https://github.com/facebook/prop-types)中看見它甚至可以客製化功能: ![](https://hackmd.io/_uploads/HyvrTAkJ6.png) 而我們這邊需要對下圖中的props做什麼檢查呢? ![](https://hackmd.io/_uploads/ry1klegJp.png) 前面提過這幾個prop就是用來在不同情況下顯示不同顏色,例如說primary是紅色、secondary是綠色......,所以說這幾個props是不可能同時有兩個以上為true的。 因此,我們這裡要檢查的內容就是「這幾個props為true的數量不能大於1」,也就是當這幾個props為true的數量大於1時,我們就應該收到error。 不過我們得如何測試這裡有幾個true?這裡會用到JavaScript中的一個特殊語法"Number"。 參考資料: > [Number MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Number) > [Number MDN 英文版](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) ![](https://hackmd.io/_uploads/HyYfkyxka.png) Number()用法中,有些較特別的用法(只寫在英文版MDN的[Number coercion部份](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#number_coercion)) ![](https://hackmd.io/_uploads/H1jUgygy6.png) 我們直接來看程式碼,傳入true會得到1、傳入false會得到0、傳入undefined會得到NaN: ```javascript! Number(true) //1 Number(false) //0 Number(undefined) //NaN ``` 不過前面有提到,我們只會有兩種狀況:寫上prop(代表該prop為true)、不寫上prop(代表該prop為undefined,亦即不會顯示) 所以這裡要再提到一個JS中的特性,這裡先來參考一篇文章:[Javascript !undefined gives true?](https://stackoverflow.com/questions/39075142/javascript-undefined-gives-true),其中提到JavaScript中 "!undefined" 會給 "true值" ![](https://hackmd.io/_uploads/rynh_JgkT.png) "[!(驚嘆號)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT)"是一個運算子,他可以幫助我們「反轉布林值」,亦即可以讓true變成false、false變成true。 可是我們可能會想,那如果驚嘆號加在非布林值前面呢?就像這裡的"!undefined" ,為什麼還可以得到true的值?undefined不是一個布林值啊。 而這又牽涉到[Trusy value](https://developer.mozilla.org/en-US/docs/Glossary/Truthy)跟[Falsy value](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),當遇到布林的上下文(boolean context,例如說if、!驚嘆號運算子......),Trusy value就會轉換成布林值true、Falsy value則轉換成布林值false。 ![](https://hackmd.io/_uploads/HkgFjyxya.png) ![](https://hackmd.io/_uploads/B1gtsJe16.png) 至於屬於Trusy value、還是Falsy value,MDN上有說:除了「false, 0, -0, 0n, "", null, undefined, and NaN」之外,其他都是Trusy value。 所以我們可以看見undefined在碰到boolean context時,會轉換為false,因此"!undefined"即為true,但我們這裡要實作的部份是希望把undefined判斷為false,因而在這裡使用兩個驚嘆號(反轉再反轉),此時"!!undefined"便在碰到boolean context時,會轉換為false。 這個時候再將"!!undefined"放進Number(),我們就可以得到回傳的"1": ![](https://hackmd.io/_uploads/SyA6nJly6.png) ### 再來終於要回到propTypes的部份了! 我們先來看看傳入prop,可以印出什麼東西 ```javascript! import PropTypes from "prop-types"; function Button({ children, primary, secondary, success, warning, danger, outline, rounded }){ return <button>{children}</button> } Button.PropTypes = { checkVariationValue: (prop) => { console.log(prop); } } export default Button; ``` 印出結果可以看到我們有五個Button元件,每一個元件都有不同的prop,例如第一個Button元件(下圖第一列)擁有success、rounded、outline、chidren的prop: ![](https://hackmd.io/_uploads/rJldAkg1T.png) 接下來我們直接看完成的coode: ```javascript! import PropTypes from "prop-types"; function Button({ children, primary, secondary, success, warning, danger, outline, rounded }){ return <button>{children}</button> } Button.propTypes = { checkVariationValue: ({primary, secondary, success, warning, danger}) => { const count = Number(!!primary) + Number(!!secondary) + Number(!!success) + Number(!!warning) + Number(!!danger) if(count > 1){ return new Error("primary, secondary, success, warning, danger這五個prop中,只能有一個為true!!!") } } } export default Button; ``` 所以當我們給Button元件新增兩種樣式(如下) ![](https://hackmd.io/_uploads/ryoslgeJT.png) 則可以在瀏覽器中取得Error的提示(如下) ![](https://hackmd.io/_uploads/rkXpxxxJ6.png) **** ## 10-160. Introducing TailwindCSS ![](https://hackmd.io/_uploads/ry0tHelka.png) Tailwind CSS使用方法 ![](https://hackmd.io/_uploads/H1w2SllJp.png) Tailwind CSS整體架構 ![](https://hackmd.io/_uploads/BJZJIgekp.png) Bulma、Bootstrap等等CSS函式庫不同於Tailwind CSS,Bulma、Bootstrap等等CSS函式庫一個className涵蓋多種css規則,而Tailwind CSS中,一個className只代表著一種css規則: ![](https://hackmd.io/_uploads/r1UiLlek6.png) **** ## 10-161. Updated Guidance for Installing and Configuring Tailwind 老師的公告: In the upcoming lecture, we will be installing and configuring Tailwind to work with Create React App. When visiting the documentation [Install Tailwind CSS with Create React App](https://tailwindcss.com/docs/guides/create-react-app) as shown in the video, you may notice a callout that CRA is no longer compatible. This guidance may be ignored as this doesn't affect our project and specific use case. Since the new docs have changed and may be confusing, included below are all the steps needed for installation and generating the tailwind and postcss config files: ``` npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p ``` **** ## 10-162. Installing Tailwind > 參考資料: [Install Tailwind CSS with Create React App](https://tailwindcss.com/docs/guides/create-react-app) 1. 上一節下載好之後,還要記得新增一段內容到`tailwind.config.js`檔案中。 ![](https://hackmd.io/_uploads/B1NhhegJT.png) **tailwind.config.js** ```javascript! module.exports = { content: [ "./src/**/*.{js,jsx,ts,tsx}", ], theme: { extend: {}, }, plugins: [], } ``` 2. 再來新增以下內容到新建立的`./src/index.css`檔案中 ![](https://hackmd.io/_uploads/Skew6leJp.png) **./src/index.css** ```javascript! @tailwind base; @tailwind components; @tailwind utilities; ``` 3. 到我們要使用Tailwind CSS的檔案`index.js`中import進`index.css` **index.js** ```javascript import "./index.css"; ``` 4. 接著在terminal重新跑: ```javascript! npm run start ``` 之後我們可以發現畫面產生了改變,例如少了margin、padding,甚至button的預設樣式也消失不見。 ![](https://hackmd.io/_uploads/SJDekbe1T.png) **** ## 10-163. How to use Tailwind [Tailwind CSS 官網](https://tailwindcss.com/docs/installation) **** ## 10-165. The ClassNames Library 看起來是用來管理class name的工具,叫做"classnames"的JS函式庫,可以透過npm下載。 > [npm classnames 網站](https://www.npmjs.com/package/classnames) ![](https://hackmd.io/_uploads/ryNup4OJT.png) 用法: ![](https://hackmd.io/_uploads/BJFLCNuya.png) 透過此函式庫的函式"classNames()",並可以傳入string(如上圖所示),經過classNames()後,這些string就會合併在一起,並且中間會以空格分開。 ### 而我們該如何與前面所提到的不同按鈕狀態合併在一起使用? ![](https://hackmd.io/_uploads/rkAf1BuJp.png) 從上圖可見,因為假設primary是false,所以bgColor是undefined,undefined經過classNames()後會直接視而不見。 ### 另一種用法: ![](https://hackmd.io/_uploads/BJHpJSOy6.png) 上圖可見,primary為true,所以只有"bg-blue-500"為true,也就是說這裡經過classNames()後只剩下"bg-blue-500"了。 ### 1. 開始使用!使用之前要先下載: ```javascript! npm install classnames ``` 2. 然後import進專案中要使用的檔案: **Button.js** ```javascript! import className from "classnames"; ``` 這裡想使用className還是classnames都可以。 **** ## 10-166. Building Some Variation 上面是App.js,下面是Button.js ![](https://hackmd.io/_uploads/rJBqQXtyT.png) **** ## 10-167. text-white Overriding Other Colors #### text-white Overriding Other Colors In the upcoming lecture, you will notice that text-white will override all other colors used in the buttons. A very quick and easy solution is to use the tailwind-merge utility library as mentioned in the thread here. > text-white這個class name會覆蓋所有其他按鈕字的顏色,以下是解決方法。 First, install the library to your project: ``` npm install tailwind-merge ``` Then, add the import to the Button.js component: ``` import { twMerge } from 'tailwind-merge'; ``` Lastly, pass your classes to twMerge: ```javascript! ... const classes = twMerge( className('px-3 py-1.5 border', { 'border-blue-500 bg-blue-500 text-white': primary, 'border-gray-900 bg-gray-900 text-white': secondary, 'border-green-500 bg-green-500 text-white': success, 'border-yellow-400 bg-yellow-400 text-white': warning, 'border-red-500 bg-red-500 text-white': danger, 'rounded-full': rounded, 'bg-white': outline, 'text-blue-500': outline && primary, 'text-gray-900': outline && secondary, 'text-green-500': outline && success, 'text-yellow-400': outline && warning, 'text-red-500': outline && danger }) ); ... ``` **** ## 10-169. Using Icons in React Projects 再來會介紹一個網站:[react-icons](https://react-icons.github.io/react-icons/) > 有很多可以使用icons的網站,但是不宜定能符合React使用方法,所以[react-icons](https://react-icons.github.io/react-icons/)的作者就把各種icons的網站集合起來,因此若我們框架為react,使用react-icons就能很輕易的添加icons! > > 1. [bootstrap icons](https://icons.getbootstrap.com/) > 2. [dev icons](https://devicon.dev/) > 3. [font awesome](https://fontawesome.com/) < 以前切版練習都用這個! > 4. [material design icons](https://fonts.google.com/icons) 1. 那就照著react-icons網站上的指引,先來下載: ``` npm install react-icons --save ``` 2. 挑一個喜歡的icon,可以看到上方會顯示你需要import的內容(這裡選擇的是Github Octicons icons): ![](https://hackmd.io/_uploads/SkCI0XKJT.png) 3. 回到App.js檔案,import以下內容,並把原先的"IconName"給成自己想要的icon名稱(這以挑GoBellFill): ```javascript! import { GoBellFill } from "react-icons/go"; ``` 4. 把icon當作元件使用: ```javascript! <GoBellFill /> ``` 我們想把icon放到Button裡面: **App.js** ```javascript! <Button success rounded outline> <GoBellFill /> Click Here ! </Button> ``` 5. 完成品: ![](https://hackmd.io/_uploads/SyBvyNFJa.png) 6. 但我還想要其他icon欸,一樣先import進來: ```javascript! import { GoBellFill, GoHubot, GoPasskeyFill } from "react-icons/go"; ``` 7. 然後一樣當作元件放入JSX中: ```javascript function App(){ return ( <div> <div> <Button success rounded outline> <GoBellFill /> Click Here ! </Button> </div> <div> <Button danger outline> <GoHubot /> Buy Now ! </Button> </div> <div> <Button warning> <GoPasskeyFill /> See Deal ! </Button> </div> <div> <Button secondary outline>Hide Ads !</Button> </div> <div> <Button primary rounded>Something !</Button> </div> </div> ) } ``` 8. 完成的樣子: ![](https://hackmd.io/_uploads/SkcSxEKJT.png) ### 我們會看到上圖雖然顯示出icon了,但是icon與字的位置好像跟我們想像的不太相符,再來就要回到Button.js中做調整。 我們要在className()的常駐css style部份再新增兩個css style:flex跟items-center: ```javascript! const classes = twMerge( className('flex items-center px-3 py-1.5 border', { 'border-blue-500 bg-blue-500 text-white': primary, 'border-gray-900 bg-gray-900 text-white': secondary, 'border-green-500 bg-green-500 text-white': success, 'border-yellow-400 bg-yellow-400 text-white': warning, 'border-red-500 bg-red-500 text-white': danger, 'rounded-full': rounded, 'bg-white': outline, 'text-blue-500': outline && primary, 'text-gray-900': outline && secondary, 'text-green-500': outline && success, 'text-yellow-400': outline && warning, 'text-red-500': outline && danger }) ); ``` 就可以看到變成我們想要的樣子了! ![](https://hackmd.io/_uploads/SkXrWVtJ6.png) 不過這個時候又覺得icon跟我的字也貼太近了吧,於是我們可以回到App.js檔案,在icon元件上加上className,再利用Tailwind中的語法:mr-1,亦即margin right 1 px。 **App.js** ```javascript! <GoBellFill className="mr-5"/> ``` 不過這裡先用mr-5,讓結果看起來更加明顯 ![](https://hackmd.io/_uploads/r1oX9NYyT.png) 這個時候,又突然懶起來,一想到每次在button中出現icon元件,都要給加上`className="mr-1"`,又長得那麼長放在程式碼中,感覺就好難維護,想到這裡就不想做了... 不過其實可以利用原生CSS去解決這個問題的,我們希望所有button底下的icon都可以加上margin-right: 1px;,所以現在的重點是如何抓出所有button底下的icon? 利用CSS選擇器中的Child combinator,符號是一個大於(>),他可以幫助我們選擇button之下的直接子層(i.e緊鄰button之下的子層) 另外如何選擇icon?我們利用svg去抓取。 至於為什麼是svg?打開dev tool,觸碰icon就可以看見它們是由svg所構成的。 ![](https://hackmd.io/_uploads/BkFI3VFJp.png) 所以完整作法如下: **index.js** ```css! button > svg{ margin-right: 5px; } ``` 成果如下: ![](https://hackmd.io/_uploads/rk0a34K1a.png) **** ## 10-170. Issues with Event Handlers 再來要處理button的點擊事件,我們馬上想到的方法就是使用prop去傳遞handleClick的函式,如下圖所示: ![](https://hackmd.io/_uploads/HyFIbrtJ6.png) 但是當我們除了要處理點擊事件,說不定還要新增滑鼠觸碰事件或是其他有的沒的事件在這個按鈕上,上方的作法就會變得相當複雜。 於是這裡會運用JS中的特殊用法,剩餘運算子(rest operator)。 > 在JS中運用點點點"...",除了這裡提到的剩餘運算子(rest operator)之外,我們也會在展開運算子(spread operator)用到,但這兩者是不同的東西,要稍微注意,這裡就不多提。 > > 可以參考:[JavaScript Rest vs Spread Operator – What’s the Difference?](https://www.freecodecamp.org/news/javascript-rest-vs-spread-operators/) 我們先來看看傳進Button元件的參數prop們,最後一行我們新放進了一個"...rest",這裡的點點點"..."就是剩餘運算子,也就是說這個rest變數代表著除了上方已經宣告的prop名稱之外的prop都屬於rest。 **Button.js** ```javascript! function Button({ children, primary, secondary, success, warning, danger, outline, rounded, ...rest }){ console.log(rest); const classes = twMerge( className('flex items-center px-3 py-1.5 border', { 'border-blue-500 bg-blue-500 text-white': primary, 'border-gray-900 bg-gray-900 text-white': secondary, 'border-green-500 bg-green-500 text-white': success, 'border-yellow-400 bg-yellow-400 text-white': warning, 'border-red-500 bg-red-500 text-white': danger, 'rounded-full': rounded, 'bg-white': outline, 'text-blue-500': outline && primary, 'text-gray-900': outline && secondary, 'text-green-500': outline && success, 'text-yellow-400': outline && warning, 'text-red-500': outline && danger }) ); return <button className={classes}>{children}</button> } ``` 印出rest,會看見5個Button元件中的第一個Button元件中,我們添加了一個onClick的prop,但是傳入Button()參數中的prop並沒有onClick這個名稱,因此他就會被assign到rest裡面: ![](https://hackmd.io/_uploads/HkjISSF1T.png) 整個過程如下圖所示: ![](https://hackmd.io/_uploads/rk1gPHKJT.png) 所以後續我們想在Button元件上新增任何prop,都可以直接在Button元件上寫上去即可,不必再改動Button.js裡的任何內容。 **** ## 10-172. Handling the Special ClassName Case 再來我們想直接在App.js中的Button元件上加入`className="mb-10"`,意味著對該Button元件增加一個margin bottom為10px的間隔。 ```javascript! <Button success rounded outline className="mb-10" onClick={handleClick}> <GoBellFill /> Click Here ! </Button> ``` 但是會發現不起作用,這是因為我們在Button.js檔案中,引入了一套classnames的函式庫,並import進來Button.js檔案中,且另外命名為"className",我們可以看見在Button.js檔案中最後return的JSX: **Button.js** ```javascript! ... return( <button {...rest} className={classes}> {children} </button> ); ... ``` 這裡也運用了className,因此這裡將{classes} assign給className,也就覆蓋了先前在App.js所assign的mb-10這個值。 解決辦法很簡單,App.js中一樣保留`className="mb-10"`: ```javascript! <Button success rounded outline className="mb-10" onClick={handleClick}> <GoBellFill /> Click Here ! </Button> ``` 而在Button.js中稍微做修改,我們利用先前放置的"...rest",將rest.className放入到className(classnames函式庫的命名)的常駐CSS Style區域: ```javascript! const classes = twMerge( className(rest.className, 'flex items-center px-3 py-1.5 border', { 'border-blue-500 bg-blue-500 text-white': primary, 'border-gray-900 bg-gray-900 text-white': secondary, 'border-green-500 bg-green-500 text-white': success, 'border-yellow-400 bg-yellow-400 text-white': warning, 'border-red-500 bg-red-500 text-white': danger, 'rounded-full': rounded, 'bg-white': outline, 'text-blue-500': outline && primary, 'text-gray-900': outline && secondary, 'text-green-500': outline && success, 'text-yellow-400': outline && warning, 'text-red-500': outline && danger }) ); ``` 如此一來我們的Button元件,也可以像是一般原生的button元件一樣操作,這可以讓其他開發者更容易讀懂我們的程式碼、也可以相當容易的去操作程式碼。