🔍 搜尋結果:"

🔍 搜尋結果:"

JavaScript 系列二:第2課 ── 從 DOM 樹移除元素、動態加上 onclick 事件

## 課程目標 能動態加上 onclick 事件屬性 能選取到父元素、子元素 能夠在 DOM 樹進行「移除」 能存取到 onclick 事件發生的元素 ## 課程內容 現在已經會在 DOM 樹增加元素、加入文字了 記得之前寫過的「事件」嗎? ``` <button onclick="myFunction()">Click me</button> ``` ``` function myFunction() { alert('你點擊了按鈕!'); } ``` 這當然也能用 JavaScript 動態加上去 ``` <button id="my-btn">Click me</button> ``` ``` function myFunction() { alert('你點擊了按鈕!'); } var btn = document.getElementById('my-btn'); btn.onclick = myFunction; ``` 在 JavaScript 裡面,像這樣定義的「函式」,它的「名稱」其實也會是一個「變數」 所以可以直接指派給 `.onclick` 屬性 覺得有點怪,沒關係,先知道可以這樣設定「事件屬性」就好 這種「把函式設定到事件屬性」的行為,我們叫「綁定」 因為在事件發生的時候,要固定執行任務,就像是綁定任務給你,固定由你來負責的感覺 --- 接著多談一下元素的父子關係 ``` <div id="app"> 這是簡介 <h1>這是標題</h1> <p id="para">這是段落文字</p> </div> ``` ``` var app = document.getElementById('app'); alert(app.children[0].textContent) alert(app.children[1].textContent) ``` 到 jsfiddle 執行看看,有看懂了嗎? 元素的 `.children` 屬性可以存取到子元素,以陣列的形式出現 別忘記陣列的索引是從 0 開始喔 接著試試這樣 ``` var para = document.getElementById('para'); alert(para.parentElement.textContent) ``` 元素的 `.parentElement` 屬性可以存取到父元素 這邊把父元素的整個內容跳出來看,像這樣跳一堆字,實務上沒什麼用 重點是,可以存取到父元素、子元素,就可以在整個 DOM 上上下下跑來跑去,用上一課教的 `.append()` 來到處新增元素啦! --- 接著,來學學如何刪除元素吧 繼續上面的範例,請在 jsfiddle 試試這段 ``` var para = document.getElementById('para'); para.remove(); ``` 然後試試這樣 ``` var app = document.getElementById('app'); app.remove(); ``` 很簡單、直觀吧! --- 最後,來學習怎麼抓到事件發生當下的元素 記得之前有學過一個 `document` 物件嗎? 這是一個 JavaScript 在瀏覽器中內建的「物件」,這個物件代表網頁「當下的狀態」,有很多資料、函數可以呼叫使用 現在來學另一個 `event` 物件,這個物件代表網頁「當下正在發生的事件」,也有很多資料、函數可以呼叫使用 ``` <button onclick="hello()">蘋果</button> <button onclick="hello()">香蕉</button> <button onclick="hello()">西瓜</button> ``` ``` function hello() { alert(event.target.textContent) } ``` 綁定函式到事件屬性之後,可以利用 `event.target` 來找到當前發生事件的元素 找到元素之後,就可以自由的存取元素屬性、或者對元素做任何你想做的事情囉! ## 課後作業 接續前一課的作業,這次要來實作「刪除事項」的功能 --- 請在事項的旁邊,加上刪除按鈕,請使用這樣的結構 ``` <ul> <li> <span>倒垃圾</span> <button>刪除</button> </li> <li> <span>繳電話費</span> <button>刪除</button> </li> <li> <span>採買本週食材</span> <button>刪除</button> </li> </ul> ``` 點擊「刪除」按鈕,會將事項刪除不見 如此一來,你的清單小程式,就可以新增事項、也可以刪除事項囉 請稍微替這個「刪除」按鈕加一點 css 屬性,弄得漂亮一點,看起來才專業 做出這個功能,你就完成這次的課程目標了!

JavaScript 系列二:第1課 ── 認識 DOM 樹、新增元素

## 課程目標 認識 DOM 樹基本觀念 能夠在 DOM 樹進行「新增」 學會基本的除錯技巧 ## 課程內容 請打開這個網站,用這個網站來練習&寫作業: https://jsfiddle.net 在學 JavaScript 的時候,你會聽到很多人在講「DOM 樹」這個名詞 什麼是「DOM 樹」? 其實只是,你在網頁上看到的東西,瀏覽器會在背後的 JavaScript 環境,維持一個對應的「DOM 樹」 比如說,當你寫這樣的 html(這邊先省略內容,只留結構) ``` <html> <head> <script></script> </head> <body> <div> <h1></h1> <p></p> </div> </body> </html ``` 瀏覽器會在背後創造一個這樣的資料結構 ``` document └── html ├── head │   └── script └── body └── div ├── h1 └── p ``` 很像是樹吧?這就是「DOM 樹」 記得之前用過的 `document.getElementById` 嗎? 這就是 DOM 最上面的 document 物件 所謂的「DOM 樹」其實就是一個「巢狀物件」。 「巢狀物件」的意思是,物件的屬性,又是另一個物件,然後這物件的屬性,又可以是另一個物件 用 JavaScript 來表示的話,類似這樣(只是概念喔,真實的物件不只是這樣,而是有一堆內建函式與屬性可以用) ``` var document = { name: 'document', children: [ { name: 'html', children: [ { name: 'head', children: [ { name: 'script', children: [] } ] }, { name: 'body', children: [ { name: 'div', children: [ { name: 'h1', children: [] }, { name: 'p', children: [] } ] } ] } ] } ] } ``` 在 DOM 中,被包起來的元素,稱為子元素;把別人包起來的元素,稱為父元素。就跟學 html 時的稱呼一樣 瀏覽器會確保用戶介面(User Interface,簡稱 UI)上看到的,跟環境中實際運行的 DOM 長得一模一樣 也就是說,你修改 UI 的內容,DOM 物件就會自動更新;你用 JavaScript 修改 DOM 物件的內容,UI 就會自動更新 --- 來學學怎麼用 JavaScript 操作 DOM 樹吧! 如果原本 html 長這樣 ``` <div id="app"></div> ``` 只要加入這段程式碼 ``` var heading = document.createElement('h1'); heading.textContent = '這是標題'; var para = document.createElement('p'); para.textContent = '這是段落文字'; var app = document.getElementById('app'); app.append(heading); app.append(para); ``` 利用 document 的內建函數 `createElement` 來動態創造新的元素 修改元素的屬性 `.textContent` 來設定文字內容 接著用 `.append()` 函數來讓元素吃下別的元素,也就是「把另一個元素收為子元素」,就跟收養子女一樣! 最後 html 就會變成這樣 ``` <div id="app"> <h1>這是標題</h1> <p>這是段落文字</p> </div> ``` 馬上貼到 jsfiddle 試試看,實驗一下、玩玩看! --- 在寫 html 的時候,出現巢狀結構是很常見的 操作 DOM 時,當然也可以自由創造巢狀結構 ``` var elem1 = document.createElement('div'); elem1.textContent = 'level 1'; var elem2 = document.createElement('div'); elem2.textContent = 'level 2'; var elem3 = document.createElement('div'); elem3.textContent = 'level 3'; var app = document.getElementById('app'); app.append(elem1); elem1.append(elem2); elem2.append(elem3); ``` 到 jsfiddle 試試看,就會清楚實際效果囉! --- 在開發的過程中,基本上會不斷打錯字、寫錯用法。程式跑不出結果,很正常 這種時候,有兩個常用的除錯方法,我們叫「debug 的方法」 第一個方法是 `alert`,你就把不確定的內容,給 alert 出來看看就對了,到處寫 alert 看一下變數內容,確定內容跟預期一樣,再接著繼續寫 第二個方法是 `console.log()`,把參數傳進去,接著打開瀏覽器的「開發者工具」,就會看到結果,通常會比 alert 內容更詳細、更方便除錯 除此之外,JavaScript 相關的各種錯誤訊息,都會在「開發者工具」出現。請去研究你所使用的瀏覽器,如何打開這個工具 ``` var x = 123; var y = 'hello'; var z = document.createElement('div'); console.log(x) console.log(y) console.log(z) ``` 請打開瀏覽器的開發者工具,實際看一下上述範例的結果。 ## 課後作業 假設你是一個健忘的人,你決定寫一個「待辦事項管理」應用程式,來幫助自己 這一課,先實作「新增事項」的功能 --- 在上面,做一個文字輸入框,旁邊有一個「新增」按鈕 在下面,做一個展示待辦事項的清單,請使用這樣的結構,id 跟 css 之類的你可以自由決定 ``` <ul> <li> <span>倒垃圾</span> </li> <li> <span>繳電話費</span> </li> <li> <span>採買本週食材</span> </li> </ul> ``` 點擊「新增」按鈕,會將新事項插入到清單最底部 請稍微替這個清單工具加一點 css 屬性,弄得漂亮一點,這樣的工具才讓人想用 --- 除此之外,請練習一下 `console.log` 的用法,不然之後遇到錯誤,幾乎沒辦法除錯 請把作業內容中,出現的變數,隨便挑三個,用 `console.log` 印到開發者工具中,然後用瀏覽器看一下內容 --- 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列一:第4課 ── 基本的陣列操作

## 課程目標 學會基本的陣列取值 能從 html 元素中,得到用戶選取的內容 ## 課程內容 這課先來學一點陣列觀念 ``` var colors = ['red', 'orange', 'yellow', 'green']; ``` 中括號 `[]` 包起來就是宣告一個陣列,並記錄到 `colors` 變數中。此陣列內容是四個字串。 陣列索引從 0~3,因為在程式設計中,索引通常是從 0 開始而不是從 1 開始。 在陣列後方,使用中括號加上索引,就可取得陣列內容的值 ``` alert(colors[0]); alert(colors[1]); ``` 這兩個 alert 會顯示出陣列中第一個元素、第二個元素,馬上到 jsfiddle 試試看就會清楚了! --- 在前幾課,我們學會了取得 `<input type="text" />` 這種元素內容的方法 文字輸入框是最常用到的網頁功能,除此之外,下拉式選單也很常用 ``` <select id="my-colors"> <option value="red">鮮豔的紅色</option> <option value="orange">美麗的橘色</option> <option value="yellow">亮眼的黃色</option> </select> <button onclick="showColor()">Click me</button> ``` 像這樣的選單,如何取得用戶選取的值呢? ``` function showColor() { var menu = document.getElementById("my-colors"); var index = menu.selectedIndex; alert(index); var value = menu.options[index].value; var text = menu.options[index].text; alert(value); alert(text); } ``` 首先一樣用 `document.getElementById` 找到我們的選單元素 選單元素這種物件,會有 `.selectedIndex` 屬性來代表目前選中的索引 我們用 alert 先把索引跳出來看一下 同時,選單元素這種物件,會有 `.options` 屬性來代表其中的 `<option>` 元素,並且會是一個陣列 這個陣列裡面,通通都是物件,一個物件代表一個 `<option>` 元素 每個 `option` 物件,又有 `.value` 屬性可供存取,以及 `.text` 屬性可供存取 工程師可以根據需要選擇 `.value` 或 `.text` 來使用 我們在寫 html select 元素時,通常會在 value 放英文單字,然後 text 放清楚的中文說明 請在 jsfiddle 試試看上面的範例,實驗一下、玩玩看,就會清楚了! ## 課後作業 接續前一課的作業,你的「線上下單」頁面,目前會顯示訂單資訊,方便顧客確認 除了顧客名稱之外,這課的作業要加強下單功能、顯示更多訂單資訊 --- 除了輸入客戶名字的欄位之外,請用 `select` 元素加上一個選單,讓用戶可以選擇服裝的分類:男裝、女裝 接著再用 `select` 元素多做一個選單,讓用戶可以選擇服裝的類型:外套、上衣、下身 這樣顧客就知道這間「成衣批發工廠」,有提供哪些商品批發了! 接著要將顧客選擇的內容,顯示在訂單資訊裡面 類似這樣: ``` ---------- |您的訂單    | |顧客姓名:XXX| |服裝分類:XXX| |服裝類型:XXX| ---------- ``` XXX的地方預設是空白,在點擊訂購按鈕之後,跳出招呼訊息之後,就把XXX改為顧客輸入、選擇的內容 請替這個訂單資訊區塊加一些 css 屬性,弄得漂亮一點 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列一:第3課 ── 修改 html 的內容

## 課程目標 學會動態修改 html 元素中的內容 ## 課程內容 在上一課,我們學會了這樣用 JavaScript 取得用戶輸入到 html 元素的內容 ``` var str = document.getElementById('keyword').value; ``` 那麼針對 `h1`, `h2`, `p` 這些用來顯示文字的元素,要如何用 JavaScript 修改其中的文字呢? 其實做法非常類似,這樣就可以了 ``` <p id="welcome">歡迎光臨</p> ``` ``` document.getElementById('welcome').textContent = '謝謝光臨'; ``` 一樣先去找出網頁中 id 為 `welcome` 的元素,接著去修改 `textContent` 這個屬性的內容 跟上一課把資料記錄在變數中類似,這邊把資料記錄在此「元素」的「屬性」之中,等於是在修改屬性內容 如前一課所說,各種 html 元素,在 JavaScript 中是一種物件 而在畫面上顯示的文字,在 JavaScript 中會將這些文字存放在物件的 `textContent` 這屬性之中 `h1`, `h2`, `p` 等等元素都可以這樣修改內容,連 `div`, `span` 這些元素也可以這樣修改內容 除了直接賦值為字串之外,使用「變數」來賦值也沒問題 ``` var str = '萬事順利'; document.getElementById('welcome').textContent = str; ``` 請在 jsfiddle 試試看上面的範例,把元素寫在 html 區塊,把修改屬性寫在 JavaScript 區塊,實驗一下、玩玩看! ## 課後作業 接續前一課的作業,你的「線上下單」頁面,目前只有簡單的招呼訊息 你希望在畫面上顯示簡單的訂單資訊,讓顧客聯絡你之前,能看著螢幕再確認一次,減少顧客下單之後又反悔的機率 這課的作業要做這個訂單資訊,先只做「顧客姓名」就好 --- 請在訂購按鈕的下方,寫一個區塊顯示訂單資訊 看起來類似這樣: ``` [我要訂購] ---------- |您的訂單    | |顧客姓名:XXX| ---------- ``` XXX的地方預設是空白,在點擊訂購按鈕之後,跳出招呼訊息之後,就把XXX改為顧客輸入的名稱 請替這個訂單資訊區塊加一些 css 屬性,弄得漂亮一點 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列一:第2課 ── 基本的變數操作、從 html 取得內容

## 課程目標 學會基本的變數賦值 學會基本的字串連接 能從 html 元素中,得到用戶輸入的內容 ## 課程內容 在上一課的內容中,我們這樣來顯示訊息視窗 ``` alert('你點擊了按鈕!'); ``` 像這樣單引號包起來的內容 `'你點擊了按鈕!'` 我們叫「字串」,是一種資料類型 除了直接把字串當參數傳給函數之外,還可以先把字串紀錄在「變數」之中,方便重複使用 ``` var message = '你點擊了按鈕!'; alert(message); ``` 就跟函數名稱一樣,變數名稱是隨便命名的,取一個自己看得懂的名稱就行了 記錄到變數之後,就可以到處重複使用,不用每次手寫整段資料內容 不論是字串或者變數,都可以連接起來使用 ``` var text1 = '恭喜'; var text2 = '發財'; alert(text1 + text2); alert('萬事' + '如意'); ``` 除了「字串」這種資料類型之外,還有「數字」等等其他類型,之後課程會提到 --- 不論是購物網站、社群網站、各種網站,一定會經常需要用戶輸入的內容 有一個簡單的方法可以取得用戶輸入內容 ``` <input id="keyword" type="text" /> <button onclick="myFunction()">Click me</button> ``` ``` function myFunction() { var str = document.getElementById('keyword').value; alert(str); } ``` 函數中的 `document` 是一個 JavaScript 在瀏覽器中內建的「物件」,這個物件代表網頁當下的狀態,有很多資料、函數可以呼叫使用 後面的 `.` 代表去呼叫這個「物件」的「函數」,或者去存取這個「物件」的「屬性」 這邊的 `document.getElementById('keyword')` 代表以 `'keyword'` 為參數去呼叫 `getElementById`函數 這函數會去找出網頁中 id 為 `keyword` 的元素,元素在 JavaScript 中也是一種物件 後面的 `.value` 就是去存取元素的 value 屬性。各種輸入相關的元素,都會把用戶的輸入內容存在 `value` 這屬性之中 使用以上的幾段程式碼,可以輕鬆地取得用戶輸入的內容 請在 jsfiddle 試試看上面的範例,把輸入欄位與按鈕寫在 html 區塊,把函數定義寫在 JavaScript 區塊,實驗一下、玩玩看! ## 課後作業 接續第一課的作業,你的「線上下單」頁面,目前有個訂購按鈕 你對按下按鈕的導購訊息,不太滿意,你希望能顯示客戶的名字,看起來更專業 這課的作業要來改善這個「線上下單」頁面的功能 --- 請使用 jsfiddle,多加一個文字輸入欄位,讓客戶能輸入名字 在客戶點擊訂購按鈕之後,跳出的訊息會顯示: 「XXX您好!謝謝您對我們的衣服有興趣!請致電 0987-654-321,會有專人提供您報價!」 請稍微替這個輸入客戶名字的欄位加一些 css 屬性,弄得漂亮一點才專業 做出以上功能,你就完成這次的課程目標了!

JavaScript 系列一:第1課 ── 基本的函數操作、onclick 事件

## 課程目標 學會定義函數的基本方法 學會呼叫函數的基本方法 學會 onclick 事件的用法 ## 課程內容 請打開這個網站,用這個網站來練習&寫作業: https://jsfiddle.net 在學 html/css 時,我們會給元素一些屬性,例如 id 與 class 屬性,連結的 href 屬性,圖片的 src 屬性 同樣的語法,還可以用來做一些互動式效果、功能 例如,可以這樣在按鈕上做出「點擊後」觸發的效果 ``` <button onclick="myFunction()">Click me</button> ``` onclick 我們叫「事件屬性」。也是屬性的一種,語法跟一般屬性相同 屬性內容是去執行 `myFunction`,後面的小括號 `()` 代表執行的意思 這邊的 `myFunction` 是所謂的「函數」。名稱是隨便命名的。根據互動的內容,取一個自己看得懂的名稱就行了 小括號裡面可以傳參數,這邊我們沒用到參數,所以直接小括號包起來 `()` 就可以 如何定義 `myFunction` 函數的內容呢?這樣即可 ``` function myFunction() { alert('你點擊了按鈕!'); alert('這是很好的開始!'); } ``` 這個函數中,使用了 JavaScript 在瀏覽器中內建的 `alert` 函數。這函數會根據參數,跳出小視窗,把參數顯示在畫面上 在 JavaScript 中,一段動作的結尾都會加上分號 `;` 用來代表這段結束 分號其實可以省略不寫,程式一樣會正常執行,但加上分號,看起來更清楚一點點 有點像寫文章時結尾的「句點」,有人習慣寫,有人經常不寫,但加了比較清楚 請在 jsfiddle 試試看上面的範例,把按鈕寫在 html 區塊,把函數定義寫在 JavaScript 區塊,實驗一下、玩玩看! ## 課後作業 假設你家裡是開成衣批發工廠的,你用 html/css 寫了一個工廠的介紹網頁 在「線上下單」頁面,你希望引導客戶聯絡你,你親口介紹、聊過才報價、提高成交率 你不想把商品報價、聯絡方式直接顯示在畫面上,你希望是點擊按鈕之後,才顯示聯絡資訊 這次作業,需要你實作這個功能。 --- 請使用 jsfiddle,做一個「我要訂購」按鈕 點擊這個按鈕,會跳出 alert 訊息顯示「謝謝您對我們的衣服有興趣!請致電 0987-654-321,會有專人提供您報價!」 接著做一個「認識工廠」按鈕 點擊這個按鈕,會跳出 alert 訊息顯示「我們工廠位於新北市,通過國際 ISO9001 認證,品質讓您放心!」 請稍微替這兩個按鈕加一點 css 屬性,弄得漂亮一點,漂亮的按鈕,會讓客戶更想點 做出這兩個按鈕,你就完成這次的課程目標了!

CSS 基本觀念:rem 跟 em 差在哪?何時該用哪個?有何注意事項?

## 簡介 CSS 是任何網站設計的重要組成部分,但理解其中一些細微差別觀念可能不容易。其中最重要的觀念之一是 rem 和 em 之間的區別,以及為什麼/何時應該使用它們。 原文出處:https://dev.to/refine/rem-vs-em-everything-you-need-to-know-5342 --- ## 預備知識 本文希望讀者對 CSS 有紮實的掌握。雖然你不必成為 CSS 大神,但如果基本的 CSS 術語你都清楚,那會好讀很多。 ## CSS 中的 em 和 rem 單位 在尋找在 CSS 中設定長度的方法時,有太多方法了。 CSS 中指定長度的所有單位都屬於兩類。 絕對長度:顧名思義,就是絕對的;它們是固定的,不會對任何事物做出反應。這意味著無論發生什麼,它們的大小都是一樣的。絕對長度包括 cm、mm、in、px、pts 和 pc。 相對長度:是指定相對於另一個單位的長度的單位,即,它們根據其他指定單位或元素做出響應。包括 %、vmax、vmin、vh、vw、ch、ex,以及我們將要討論的單位 em 和 rem。 如果您想了解更多關於這些單位的訊息,可以現在先去查一查。現在讓我們看看這兩個單位。 ## 什麼是 CSS em 就像我上面說的,CSS 中的 em 單位是用於調整網頁元素大小的相對測量單位,主要是字體大小。因為是相對於父元素的,所以1em等於父元素中設置的font-size。 這意味著如果您將父 div 中的字體大小設置為 20px,並將子 div 中的字體大小設置為 2em,則子 div 中的字體大小將等於 40px。這是一個例子。 首先,讓我們編寫 HTML ``` <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="parent"> I'm parent div set to 20px <div class="child"> I'm the child div set to 2em, i.e 40px. </div> </div> </body> </html> ``` Next, the CSS. ``` .parent{ font-size: 20px; } .child{ font-size: 2em; } p { font-size: 1.5em; } ``` 那會給我們這個。 ![](https://refine.ams3.cdn.digitaloceanspaces.com/blog/2022-12-21-em-vs-rem/em-vs-rem-1.png) em 單位很有用,因為它允許您根據先前宣告的元素的字體大小調整頁面上元素的大小,這有助於建立一致的視覺層次結構。這對於建立易於視障用戶閱讀的可存取網站非常有用。 需要注意的是,如果不指定父元素的值,則以瀏覽器的預設值作為父元素。 ``` p { font-size: 1.5em; } ``` 在此範例中,字體大小屬性設置為 1.5em,這意味著如果沒有直接父元素,`<p>` 元素中的文本大小將是瀏覽器預設字體大小大小的 1.5 倍。 由於大多數瀏覽器根據螢幕大小縮放其預設字體大小,這使您可以建立靈活且響應迅速的佈局,以適應不同的螢幕和字體大小。 使用適當的 CSS 屬性,em 單位也可用於設置其他元素的大小,例如邊距、填充和邊框。 ## 什麼是 CSS rem 現在我們知道 em 是什麼,讓我們看看 rem。 rem 是 CSS 中另一個測量長度的單位,代表“root em”。由於我們知道一個 em 等於當前字體大小的數值,因此我們可以推斷出“根 em”指的是根元素的字體大小,通常是 `<html>` 元素。 很困惑嗎?讓我們進一步拆解它。 與 em 一樣,rem 從父元素繼承其大小,但 rem 查看的父元素不是它上面的 div 或部分,而是包圍它的第一個元素,即 html 元素。讓我們用前面的程式碼做一個例子。相同的 html 程式碼,只是多了一個 div。 ``` <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="style.css"> </head> <body> <div class="parent"> I'm parent div set to 20px <div class="child"> I'm the child div set to 2em, i.e 40px. </div> <div class="child-2"> I'm the child div set to 2em, i.e 60px. </div> </div> </body> </html> ``` 接下來,我們將以下 CSS 程式碼加入到我們的 CSS 文件中。 ``` html{ font-size: 30px; } .child-2{ font-size: 2rem; } ``` 結果會是這樣。 ![Image em](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njnny8zjwbk3157hece0.png) 如您所見,儘管 child-2 div 在另一個 div 中,但它會一直往上找到 html 元素以繼承其字體大小。 使用“rem”單位允許以更具可擴展性和靈活性的方式來調整頁面上的元素大小,因為如果您更改根元素的字體大小,所有使用“rem”單位調整大小的元素將自動更新以保持它們的相對大小尺寸。 ## em 和 rem 單位的區別 到目前為止,您已經知道 em 和 rem 之間的區別,但為了清楚起見,我想重申一下這兩個值之間的區別。 在 CSS 中,rem 單位僅相對於文檔的根元素,而 em 單位僅相對於目標元素的直接父元素。這意味著 em 大小繼承自父元素,而 rem 大小僅繼承自根元素。 ## 何時在 CSS 中使用 em 和 rem 單位 對字體大小、邊距和填充等全局值使用 rem 單位是一個好主意,特別是如果你想為整個文檔指定一個字體大小並讓它統一縮放而不是受字體影響父元素的大小。 em 更適合特定於特定元素及其子元素的值。這使您可以建立一致且靈活的佈局,以適應不同的螢幕尺寸和字體大小。 ## 在 CSS 中使用 em 和 rem 單位的潛在問題 em 和 rem 是目前指定長度時使用的最佳單位,但就像生活中的所有事物一樣,它們並不完美。以下是您在使用 em 和 rem 時可能會遇到的幾個問題: - **複雜的計算:**使用 em 和 rem 單位會導致複雜的計算,尤其是當涉及嵌套元素時。這會使準確預測和控制頁面上元素的大小變得困難。 - **繼承問題:** 因為 em 單位是相對於其父元素的字體大小的,所以很難理解和控制如何在頁面上繼承大小。這可能會導致意外結果,需要額外除錯才能解決。 - **性能問題:**在極少數情況下,使用 em 和 rem 單位會對性能產生負面影響,尤其是在與復雜計算結合使用或在頁面上過度使用時。 總的來說,雖然 em 和 rem 單位在某些情況下會有所幫助,但重要的是要仔細考慮它們的潛在缺點以及它們是否是您專案的最佳選擇。

20 個冷門、但很實用的 git 指令:值得你稍微認識一下

如果您曾經瀏覽過 [git 手冊](https://git-scm.com/docs)(或執行 `man git`),那麼您會發現 git 指令比我們每天在用的多很多。很多指令非常強大,可以讓你的生活更輕鬆(有些比較小眾,但知道一下還是不錯)。 > 這篇文章整理了我最喜歡的 20 個冷門 git 功能,您可以使用來改善您的開發流程、給您的同事留下深刻印象、幫助您回答 git 面試問題,最重要的是 - 可以玩得很開心! 原文出處:https://dev.to/lissy93/20-git-commands-you-probably-didnt-know-about-4j4o --- ## Git Web > 執行 [`git instaweb`](https://git-scm.com/docs/git-instaweb) 可以立即瀏覽 gitweb 中的工作存儲庫 Git 有一個內建的[基於網路可視化工具](https://git-scm.com/docs/gitweb) 可以瀏覽本地存儲庫,它允許您通過瀏覽器中的 GUI 查看和管理您的存儲庫。它包含許多有用的功能,包括: - 瀏覽和單步執行修訂並檢查差異、文件內容和元資料 - 可視化查看提交日誌、分支、目錄、文件歷史和附加資料 - 生成提交和存儲庫活動日誌的 RSS 或 Atom 提要 - 搜尋提交、文件、更改和差異 要打開它,只需從您的存儲庫中執行 `git instaweb`。您的瀏覽器應該會彈出並讀取 http://localhost:1234 。如果您沒有安裝 Lighttpd,您可以使用“-d”標誌指定一個備用 Web 伺服器。其他選項可以通過標誌配置(例如 `-p` 用於端口,`-b` 用於打開瀏覽器等),或在 git 配置中的 `[instaweb]` 塊下配置。 還有 `git gui` 命令,它可以打開一個基於 GUI 的 git 應用程式 ![](https://i.ibb.co/0DrmcWG/Screenshot-from-2022-12-17-20-26-30.png) --- ## Git Notes > 使用 [`git notes`](https://git-scm.com/docs/git-notes) 向提交加入額外訊息 有時您需要將其他資料附加到 git 提交(除了更改、訊息、日期時間和作者訊息之外)。 註釋存儲在 .git/refs/notes 中,由於它與提交對像資料是分開的,因此您可以隨時修改與提交關聯的註釋,而無需更改 SHA-1 哈希。 您可以使用 `git log`、使用大多數 git GUI 應用程式或使用 `git notes show` 命令查看筆記。一些 git 主機還在提交視圖中顯示註釋(儘管 [GH 不再顯示註釋](https://github.blog/2010-08-25-git-notes-display/))。 --- ## Git Bisect > 使用 [`git bisect`](https://git-scm.com/docs/git-bisect) 你可以使用二進制搜尋找到引入錯誤的提交 這是最強大又好用的 git 命令之一 - bisect 在除錯時絕對是救命稻草。開始對分後,它會為您檢查提交,然後您告訴它提交是“好”(沒有錯誤)還是“壞”(引入錯誤),這可以讓您縮小最早提交的錯誤。 請執行 `git bisect start`,然後使用 `git bisect good <commit-hash>` 向其傳遞一個已知的良好提交,並使用 `git bisect bad <optional-hash>` 傳遞一個已知的錯誤提交(預設為當前)。然後它會檢查好提交和壞提交之間的提交,然後你用 `git bisect good` 或 `git bisect bad` 指定錯誤存在與否。然後它會重複這個過程,在好與壞的中心檢查一個提交,一直到你找到引入錯誤的確切提交。隨時使用 `git bisect reset` 取消。 bisect 命令還有更多功能,包括回放、查看提交、跳過,因此下次除錯時值得查看文檔。 --- ## Git Grep > 使用 [`git grep`](https://git-scm.com/docs/git-grep) 在您的存儲庫中搜尋程式碼、文件、提交或任何其他內容 有沒有發現自己需要在 git 專案中的任何地方搜尋字串?使用 git grep,您可以輕鬆地在整個專案中和跨分支搜尋任何字串或 RegEx(例如更強大的 <kbd>Ctrl</kbd> + <kbd>F</kbd>!)。 `git grep <regexp> <ref>` 它包括大量 [選項](https://git-scm.com/docs/git-grep#_options) 來縮小搜尋範圍,或指定結果格式。例如,使用 `-l` 僅返回文件名,`-c` 指定每個文件返回的匹配數,`-e` 排除匹配條件的結果,`--and` 指定多個條件,` -n` 以行號搜尋。 由於 git grep 與正則表達式兼容,因此您可以使用搜尋的字串獲得更多進階訊息。 您還可以使用它來指定文件擴展名,例如 `git grep 'console.log' *.js`,它將顯示 JavaScript 文件中的所有 console.logs 第二個參數是一個 ref,可以是分支名稱、提交、提交範圍或其他任何內容。例如。 `git grep "foo" HEAD~1` 將搜尋之前的提交。 --- ## Git Archive > 使用 [`git archive`](https://git-scm.com/docs/git-archive) 將整個 repo 合併到一個文件中 共享或備份存儲庫時,通常首選將其存儲為單個文件。使用 git archive 將包括所有 repo 歷史記錄,因此可以輕鬆將其提取回其原始形式。該命令還包括許多附加選項,因此您可以準確自定義存檔中包含和不包含的文件。 ``` git archive --format=tar --output=./my-archive HEAD ``` --- ## Git Submodules > 使用 [`git submodule`](https://git-scm.com/docs/git-submodule) 將任何其他存儲庫拉入您的存儲庫 在 git 中,[submodules](https://git-scm.com/docs/gitsubmodules) 讓您可以將一個存儲庫掛載到另一個存儲庫中,通常用於核心依賴項或將組件拆分到單獨的存儲庫中。有關詳細訊息,請參閱[這篇文章](https://notes.aliciasykes.com/17996/quick-tip-git-submodules)。 執行以下命令會將模塊拉到指定位置,並建立一個 .gitmodules 文件,以便在複製 repo 時始終下載它。複製 repo 時使用 `--recursive` 標誌來包含子模塊。 ``` git submodule add https://github.com/<user>/<repo> <path/to/save/at> ``` 還有 [`git subtree`](https://www.atlassian.com/git/tutorials/git-subtree),它做類似的事情,但不需要元資料文件。 --- ## Git Bug Report > 使用 [`git bugreport`](https://git-scm.com/docs/git-bugreport) 編寫錯誤票,包括 git 和系統訊息 此命令將捕獲系統訊息,然後打開一個標準錯誤模板(重現步驟、實際 + 預期輸出等)。完成的文件應該是一個非常完整的錯誤報告,包含所有必要的訊息。 如果您是開源包的維護者並要求用戶(開發人員)提出錯誤報告,這將非常方便,因為它確保包含所有必要的資料。 如果您向核心 git 系統提交錯誤報告,您還可以執行 [`git diagnostic`](https://git-scm.com/docs/git-diagnose) 命令,然後提出您的問題 [這裡](https://github.com/git/git)。 --- ## Git Fsck > 使用 [`git fsck`](https://git-scm.com/docs/git-fsck) 檢查所有物件,或恢復無法存取的物件 雖然不常需要,但有時您可能必須驗證 git 存儲的物件。這就是 fsck(或文件系統檢查)發揮作用的地方,它測試對像資料庫並驗證所有物件的 SHA-1 ID 及其建立的連接。 它還可以與 `--unreachable` 標誌一起使用,以查找不再可以從任何命名引用存取的物件(因為與其他命令不同,它包括 `.git/objects` 中的所有內容)。 --- ## Git Stripspace > 使用 [`git stripspace`](https://git-scm.com/docs/git-stripspace) 格式化給定文件中的空格 最佳做法是避免在行尾尾隨空格,避免有多個連續的空行,避免輸入的開頭和結尾出現空行,並以新行結束每個文件。有很多特定於語言的工具可以自動為您執行此操作(例如 prettier),但 Git 也內置了此功能。 它用於元資料(提交訊息、標籤、分支描述等),但如果您將文件通過管道傳輸給它,然後將響應通過管道傳輸回文件,它也可以工作。例如。 `cat ./path-to-file.txt | git stripspace` 或 `git stripspace < dirty-file.txt > clean-file.txt` 您還可以使用它來刪除註釋(使用 `--strip-comments`),甚至註釋掉行(使用 `--comment-lines`)。 --- ## Git Diff > 使用 [`git diff`](https://git-scm.com/docs/git-diff) 你可以比較 2 組程式碼之間的差異 您可能知道您可以執行 `git diff` 來顯示自上次提交以來的所有更改,或者使用 `git diff <commit-sha>` 來比較 2 次提交或 1 次提交到 HEAD。但是您可以使用 diff 命令做更多的事情。 您還可以使用它來比較任意兩個任意文件,使用 `diff file-1.txt file-2.txt`(不再存取 [diffchecker.com](https://www.diffchecker.com/compare/)! ) 或者使用 `git diff branch1..branch2` 相互比較 2 個分支或引用 請注意,雙點 (`..`) 與空格相同,表示 diff 輸入應該是分支的尖端,但您也可以使用三點 (`...`) 來轉換第一個參數進入兩個差異輸入之間共享的共同祖先提交的引用 - 非常有用!如果只想跨分支比較單個文件,只需將文件名作為第三個參數傳遞。 您可能希望查看在給定日期範圍內所做的所有更改,為此使用 `git diff HEAD@{7.day.ago} HEAD@{0}`(上週),同樣可以將其與文件名、分支名稱、特定提交或任何其他參考。 還有 [`git range-diff`](https://www.git-scm.com/docs/git-range-diff) 命令,它提供了一個用於比較提交範圍的簡單界面。 git diff 工具還有更多功能(以及使用您自己的差異檢查器的選項),因此我建議查看 [文檔](https://git-scm.com/docs/git-diff#_description) . --- ## Git Hooks > 使用 [`hooks`](https://git-scm.com/docs/githooks) 在給定的 get 操作發生時執行命令或執行腳本 Hooks 可以讓你自動化幾乎所有的事情。例如:確保滿足標準(提交訊息、分支名稱、補丁大小)、程式碼質量(測試、lint)、將附加訊息附加到提交(用戶、設備、票證 ID)、呼叫 webhook 來記錄事件或執行管道等 對於大多數 git 事件,如 commit, rebase, merge, push, update, applypatch 等,都有前後 [hooks available](https://git-scm.com/docs/githooks)。 鉤子存儲在 `.git/hooks` 中(除非您使用 `git config core.hooksPath` 在其他地方配置它們),並且可以使用 [`git hook`](https://git-scm.com/docs) 進行測試/git-hook) 命令。由於它們只是 shell 文件,因此可用於執行任何命令。 掛鉤不會推送到遠程存儲庫,因此要在您的團隊中共享和管理它們,您需要使用 [掛鉤管理器](https://github.com/aitemr/awesome-git-hooks#tools) ,例如 [lefthook](https://github.com/evilmartians/lefthook) 或 [husky](https://github.com/typicode/husky)。還有幾個[3rd-party tools](https://githooks.com/#projects),這使得管理鉤子更容易,我推薦[overcommit](https://github.com/sds/overcommit)。 請記住,掛鉤總是可以跳過(使用 `--no-verify` 標誌),所以永遠不要純粹依賴掛鉤,尤其是對於任何與安全相關的事情。 --- ## Git Blame > 使用 [`git blame`](https://git-scm.com/docs/git-blame) 顯示特定修訂版和行的作者訊息 一個經典的,快速找出誰寫了特定程式碼行(也就是你的哪個同事應該為這個錯誤負責!)。但它也有助於確定在哪個時間點發生了某些更改並檢查該提交和關聯的元資料。 例如,要查看 index.rs 第 400 到 420 行的作者和提交訊息,您可以執行: ``` git blame -L 400,420 index.rs ``` --- ## Git LFS > 使用 [`git lfs`](https://git-lfs.github.com/) 存儲大文件,以免拖慢您的存儲庫 您的專案通常會包含較大的文件(例如資料庫、二進制資產、檔案或媒體文件),這會減慢 git 工作流程並使使用限制達到最大。這就是 [大型文件存儲](https://git-lfs.github.com/) 的用武之地 - 它使您能夠將這些大型資產存儲在其他地方,同時使它們可以通過 git 進行跟踪並保持相同的存取控制/權限。 LFS 的工作原理是將這些較大的文件替換為在 git 中跟踪的文本指針。 要使用它,只需執行 `git lfs track <file glob>`,這將更新您的 `.gitattributes` 文件。您可以通過擴展名(例如“*.psd”)、目錄或單獨指定文件。執行 git lfs ls-files 以查看跟踪的 LFS 文件列表。 --- ## Git GC > 使用 [`git gc`](https://git-scm.com/docs/git-gc) 優化您的存儲庫 隨著時間的推移,git repos 會積累各種類型的垃圾,這些垃圾會佔用磁盤空間並減慢操作速度。這就是內置垃圾收集器的用武之地。執行 `git gc` 將刪除孤立的和不可存取的提交(使用 [`git prune`](https://git-scm.com/docs/git-prune)),壓縮文件修訂和存儲的 git 物件,以及一些其他一般的內務管理任務,如打包引用、修剪引用日誌、尊重元資料或陳舊的工作樹和更新索引。 加入 `--aggressive` 標誌將 [積極優化](https://git-scm.com/docs/git-gc#_aggressive) 存儲庫,丟棄任何現有的增量並重新計算它們,這需要更長的時間執行但如果你有一個大型存儲庫可能需要。 --- ## Git Show > 使用 [`git show`](https://git-scm.com/docs/git-show) 輕鬆檢查任何 git 物件 以易於閱讀的形式輸出物件(blob、樹、標籤或提交)。要使用,只需執行 `git show <object>`。您可能還想附加 `--pretty` 標誌,以獲得更清晰的輸出,但還有許多其他選項可用於自定義輸出(使用 `--format`),因此此命令對於準確顯示非常強大你需要什麼。 這非常有用的一個實例是在另一個分支中預覽文件,而無需切換分支。只需執行 `git show branch:file` --- ## Git Describe > 使用 [`git describe`](https://git-scm.com/docs/git-describe) 查找可從提交中存取的最新標記,並為其指定一個人類可讀的名稱 執行 `git describe`,您將看到一個人類可讀的字串,該字串由最後一個標籤名稱與當前提交組合而成,以生成一個字串。您還可以將特定標籤傳遞給它, 請注意,您必須已建立標籤才能使其正常工作,除非您附加了 `--all` 標誌。默認情況下,Git describe 也只會使用帶註釋的標籤,因此您必須指定 `--tags` 標誌以使其也使用輕量級標籤。 --- ## Git Tag > 使用 [`git tag`](https://git-scm.com/docs/git-tag) 在你的 repo 歷史中標記一個特定點 能夠[標記](https://git-scm.com/book/en/v2/Git-Basics-Tagging) 存儲庫歷史記錄中最常用於表示發布版本的特定重要點通常很有用。建立標籤就像 `git tag <tagname>` 一樣簡單,或者您可以使用 `git tag -a v4.2.0 <commit sha>` 標記歷史提交。與提交一樣,您可以使用“-m”在標籤旁邊包含一條訊息。 不要忘記使用 `git push origin <tagname>` 將您的標籤推送到遠程。 要列出所有標籤,只需執行 `git tag`,並可選擇使用 `-l` 進行通配符搜尋。 然後,您將能夠使用 `git checkout <tagname>` 檢出特定標籤 --- ## Git Reflog > 使用 [`git reflog`](https://git-scm.com/docs/git-reflog) 列出對您的存儲庫所做的所有更新 Git 使用稱為參考日誌或“reflogs”的機制跟踪分支尖端的更新。跟踪各種事件,包括:克隆、拉取、推送、提交、檢出和合併。能夠找到事件引用通常很有用,因為許多命令都接受引用作為參數。只需執行 `git reflog` 即可查看 `HEAD` 上的最近事件。 reflog 真正有用的一件事是恢復丟失的提交。 Git 永遠不會真正丟失任何東西,即使是在重寫歷史時(比如變基或提交修改)。 Reflog 允許您返回提交,即使它們沒有被任何分支或標記引用。 默認情況下,reflog 使用 `HEAD`(您當前的分支),但您可以在任何 ref 上執行 reflog。例如 `git reflog show <branch name>`,或者使用 `git reflog stash` 查看隱藏的更改。或者使用 `git reflog show --all` 顯示所有引用 --- ## Git Log > 使用 [`git log`](https://git-scm.com/docs/git-log) 查看提交列表 您可能已經熟悉執行 `git log` 來查看當前分支上最近提交的列表。但是您可以使用 git log 做更多的事情。 使用 `git log --graph --decorate --oneline` 將顯示一個漂亮整潔的提交圖以及 ref 指針。 ![示例 git 日誌輸出](https://i.ibb.co/c1WByg8/Screenshot-from-2022-12-17-20-43-56.png) 您還經常需要能夠根據各種參數過濾日誌,其中最有用的是: - `git log --search="<anything>"` - 搜尋特定程式碼更改的日誌 - `git log --author="<pattern>"` - 只顯示特定作者的日誌 - `git log --grep="<pattern>"` - 使用搜尋詞或正則表達式過濾日誌 - `git log <since>..<until>` - 顯示兩個引用之間的所有提交 - `git log -- <file>` - 顯示僅對特定文件進行的所有提交 或者,只需執行 `git shortlog` 以獲得匯總的提交列表。 --- ## Git Cherry Pick > 使用 [`git cherry-pick`](https://git-scm.com/docs/git-cherry-pick) 通過引用選擇指定的提交並將它們附加到工作 HEAD 有時你需要從其他地方拉一個特定的提交到你當前的分支。這對於應用熱修復、撤消更改、恢復丟失的提交以及在某些團隊協作設置中非常有用。請注意,通常傳統的合併是更好的做法,因為挑選提交會導致日誌中出現重複提交。 用法很簡單,只需執行 `git cherry-pick <commit-hash>`。這會將指定的提交拉入當前分支。 --- ## Git Switch > 使用 [`git switch`](https://git-scm.com/docs/git-switch) 在分支之間移動是我們經常做的事情,`switch` 命令就像是`git checkout` 的簡化版本,它可以用來建立和在分支之間導航,但不像 checkout 在分支之間移動時不會復制修改的文件. 類似於 `checkout -b`,使用 switch 命令你可以附加 `-c` 標誌來建立一個新分支,然後直接跳入其中,例如`git switch -c <新分支>`。執行 `git switch -` 將放棄您所做的任何實驗性更改,並返回到您之前的分支。 --- ## Git Standup > 使用 [`git standup`](https://github.com/kamranahmedse/git-standup) 回憶你在最後一個工作日做了什麼,基於 git 提交 我把它放在最後,因為它不包含在大多數 git 客戶端中,但是您可以使用系統包管理器[輕鬆安裝](https://github.com/kamranahmedse/git-standup#install) ,使用 1 行 curl 腳本,或從源程式碼建置。 如果您的老闆要求您每天站立一次,以更新昨天的工作,但您永遠記不起自己到底做了什麼——這個適合您!它將顯示一個格式良好的列表,列出在給定時間範圍內完成的所有事情。用法很簡單,只需執行 `git standup`,或使用 [這些選項](https://github.com/kamranahmedse/git-standup#options) 指定應顯示哪些資料(作者、時間範圍、分支機構等)。 --- ## 結論 希望對您有幫助!

關於 TypeScript 中 Utility Types 的小知識:新手推薦

TypeScript 的型別系統很強大,大部分專案都在用。此外,在 TypeScript 中,其實有提供一些 Utility Types 來輔助我們進行型別定義與操作。今天與您分享其中五個。 - 原文出處:https://dev.to/murillonahvs/a-little-about-typescript-utility-types-1epd ### Summary - Pick - Omit - ReadOnly - Partial - Required --- ## 1. Pick(Type, Keys) Pick utility type 是從現有型別中選取某些屬性來生成新的型別。 基本上,Pick 從指定型別中刪除除指定鍵之外的所有鍵。 ``` type Person = { name: string lastName: string age: number hobbies: string } type SomePerson = Pick<Person, "name" | "age"> // type SomePerson = { // name: string; // age: number; // } ``` --- ## 2. Omit(Type, Keys) Omit utili type 與 Pick 型別相反,Omit 是寫出要省略的屬性,而不是要保留的屬性。 ``` type Person = { name: string lastName: string age: number hobbies: string } type SomePerson = Omit<Person, "lastName" | "hobbies"> // type SomePerson = { // name: string; // age: number; // } ``` --- ## 3. Readonly(Type) Readonly utility type 用於所有屬性都設置成唯讀的型別。無法為屬性分配新值,不然就跳 TypeScript 警告。 ``` type Person = { name: string } type ReadOnlyPerson = Readonly<Person> const person: ReadOnlyPerson = { name: "Fizz", } person.name = "Buzz" // Cannot assign to 'name' because it is a read-only property. ``` --- ## 4. Partial(Type) Partial utility type 用於所有屬性都設置為選填的型別。 ``` type Person = { name: string lastName: string age: number address: string } type PartialPerson = Partial<Person> // type PartialPerson = { // name?: string | undefined; // lastName?: string | undefined; // age?: number | undefined; // address?: string | undefined; // } ``` --- ## 5. Required(Type) Required utility type 與 Partial 相反。要求要定義好所有屬性。可用來避免選填屬性出現在此型別中。 ``` type Person = { name?: string lastName?: string age?: number address?: string } type RequiredPerson = Required<Person> // type RequiredPerson = { // name: string; // lastName: string; // age: number; // address: string; // } ``` --- 以上,簡單舉例五個分享,有很多好用的 utility type,歡迎查看官網了解更多: https://www.typescriptlang.org/docs/handbook/utility-types.html

寫出更好 JavaScript 的幾個實務技巧:新手推薦

JavaScript 寫起來有很大彈性,但如何改善自己的 JavaScript 品質呢?以下是一份實用的參考指南。 - 原文出處:https://dev.to/taillogs/practical-ways-to-write-better-javascript-26d4 # 使用 TypeScript 改善 JS 的第一個技巧就是,不要寫 JS。寫 TypeScript 吧。它是微軟寫的一個語言,算是 JS 的母集合(一般的 JS 可以直接在 TS 裡面跑)。TS 在 JS 之上添加了一個全面的型別系統。長期下來,整個前端生態系,幾乎都支援 TS 語言了。下面介紹一下 TS 的優點。 **TypeScript 可以確保「型別安全」** 型別安全代表編譯器驗證了所有型別是否以「合法」的方式被使用。換句話說,如果你創建一個接受「數字」的函數 `foo`: ``` function foo(someNum: number): number { return someNum + 5; } ``` 該「foo」函數呼叫時要傳一個數字: 正確用法 ``` console.log(foo(2)); // prints "7" ``` 錯誤用法 ``` console.log(foo("two")); // invalid TS code ``` 除了需要花時間加型別以外,沒有其他缺點了。好處則是顯而易見。型別安全針對常見錯誤提供了額外預防,這對於像 JS 這樣鬆散的語言來說,是一件好事。 **TypeScript 的型別系統,讓你能重構大型應用程式** 重構大型 JS 應用程式是一場噩夢。主要是因為它不會檢查函數簽名。也就是說,JS函數隨便呼叫,編譯器都不會先報錯。舉個例,如果我有一個函數「myAPI」,由 1000 個不同的服務使用: ``` function myAPI(someNum, someString) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } } ``` 我稍微更改了呼叫簽名: ``` function myAPI(someString, someNum) { if (someNum > 0) { leakCredentials(); } else { console.log(someString); } } ``` 我必須自已 100% 去確認,使用此功能的1000 個地方,都有正確更新用法。漏掉的地方,在執行時就會壞掉。在 TS 中相同的情境如下: before ``` function myAPITS(someNum: number, someString: string) { ... } ``` after ``` function myAPITS(someString: string, someNum: number) { ... } ``` 如您所見,「myAPITS」函數跟在 JavaScript 中一樣修改。但是,這段程式碼無法順利產出 JavaScript,而是會出現無效 TypeScript 的錯誤提示,因為用到它的1000個地方,現在型別就出錯了。正是因為「型別安全」,這 1000 個案例會阻止編譯。 **TypeScript 使團隊溝通架構更容易** 正確設定 TS 後,要先定義介面跟類型,不然很難寫程式碼。這也順便提供了一種簡潔、可溝通的架構提案方法。例如,如果我想跟後端提出新的「請求」類型,可以使用 TS 將以下內容提交給團隊。 ``` interface BasicRequest { body: Buffer; headers: { [header: string]: string | string[] | undefined; }; secret: Shhh; } ``` 雖然也是要寫一點程式碼,但可以先提供以上內容作為初步想法、取得回饋,而不用一次設計完功能。像這樣強迫開發者先定義介面跟 API,能讓程式碼品質更好。 總體而言,TS 已經發展成為一種成熟且更可預測的 vanilla JS 替代品。當然還是要熟 JS,但我現在大多數新專案都是直接寫 TS。 # 使用現代功能 JavaScript是世界上最流行的程式設計語言之一。你可能會以為這語言已經被百萬開發者摸透了,其實不是。很多新功能是近期才加上去的。 **`async` 與 `await`** 長期以來,非同步、事件驅動的 callback 是 JS 開發中不可避免的: 傳統 callback ``` makeHttpRequest('google.com', function (err, result) { if (err) { console.log('Oh boy, an error'); } else { console.log(result); } }); ``` 上述程式碼會有越寫越多層的問題。JS 添加了一個新概念叫 Promises,可以避免嵌套問題。 Promises ``` makeHttpRequest('google.com').then(function (result) { console.log(result); }).catch(function (err) { console.log('Oh boy, an error'); }); ``` 與 callback 相比,Promise 的最大優勢是可讀性和可鏈性。 雖然 Promise 很棒,但它們仍然有一些不足之處。寫起來感覺有點怪。為了解決這個問題,ECMAScript 委員會添加一種使用 promise 的新方法,`async` 和 `await`: `async` 與 `await` ``` try { const result = await makeHttpRequest('google.com'); console.log(result); } catch (err) { console.log('Oh boy, an error'); } ``` 需要注意的是,`await` 的內容必須先被宣告為 `async`: 以前面 makeHttpRequest 舉例 ``` async function makeHttpRequest(url) { // ... } ``` 也可以直接 `await` 一個 Promise,因為 `async` 函數實際上只是 Promise 比較花哨的包裝寫法。這也意味著,`async/await` 代碼和 Promise 代碼在功能上是等價的。因此,請大方使用 `async/await` 吧。 **`let` and `const`** 以前大家寫 JS 都只能用 `var`。`var` 的變數作用域有一些獨特規則,一直讓人很困惑。從 ES6 開始,有了 `const` 跟 `let` 可以替代使用,幾乎不用再寫 `var` 了。 至於何時要用 const 何時要用 let 呢?永遠從 const 先開始使用。const 因為不可修改、更可預期,會讓程式碼品質更好。使用 let 的場景比較少,我寫 const 的頻率比 let 高 20 倍左右。 **箭頭 `=>` 函數** 箭頭函數是在JS中宣告匿名函數的簡潔方法。通常作為 callback 或 event hook 傳遞。 傳統的匿名 function ``` someMethod(1, function () { // has no name console.log('called'); }); ``` 在大多數情況下,這種風格沒有任何“不對”。但它的變數作用域很“有趣”,常導致許多意想不到的錯誤。有了箭頭函數之後,就沒這問題了。 匿名箭頭函數 ``` someMethod(1, () => { // has no name console.log('called'); }); ``` 除了更簡潔之外,箭頭函數還具有更實用的變數範圍界定。箭頭函數從定義它們的作用域繼承「this」。 在某些情況下,箭頭函數甚至可以更簡潔: ``` const added = [0, 1, 2, 3, 4].map((item) => item + 1); console.log(added) // prints "[1, 2, 3, 4, 5]" ``` 箭頭函數可以包括隱式的「return」語句。就不用寫大括弧、分號。 不過,這跟“var”被完全取代不同。傳統匿名函數仍然有需要的地方,例如類別方法定義。話雖如此,如果你總是預設使用箭頭函數,程式碼錯誤會少很多。 **展開運算子 `...`** 提取一個物件的鍵/值對,並將它們添加為另一個物件的子物件,是一種很常見的場景。從歷史上看,有幾種方法可以實現,但通通都很笨拙: ``` const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; const merged = Object.assign({}, obj1, obj2); console.log(merged) // prints { dog: 'woof', cat: 'meow' } ``` 這寫法以前很常見,也很囉唆。多虧了「展開運算子」,再也不需要那樣寫了: ``` const obj1 = { dog: 'woof' }; const obj2 = { cat: 'meow' }; console.log({ ...obj1, ...obj2 }); // prints { dog: 'woof', cat: 'meow' } ``` 最棒的是,與陣列也可無縫接軌: ``` const arr1 = [1, 2]; const arr2 = [3, 4]; console.log([ ...arr1, ...arr2 ]); // prints [1, 2, 3, 4] ``` 它可能不是近期 JS 中最重要的功能,但它是我的最愛之一。 **範本文字(範本字串)** 字串處理太常見了,程式語言要能處理範本字串,才夠好用。處理動態內容、多行文字時,都會需要它: ``` const name = 'Ryland'; const helloString = `Hello ${name}`; ``` 我認為程式碼不言自明。看起來好多了。 **物件解構** 物件解構是一種從資料集合(物件、陣列等)中提取值,而無需反覆運算數據或顯式訪問其鍵的方法: 老方法 ``` function animalParty(dogSound, catSound) {} const myDict = { dog: 'woof', cat: 'meow', }; animalParty(myDict.dog, myDict.cat); ``` 新方法 ``` function animalParty(dogSound, catSound) {} const myDict = { dog: 'woof', cat: 'meow', }; const { dog, cat } = myDict; animalParty(dog, cat); ``` 不只這樣。您還可以在函數的簽名中定義解構: 解構範例二 ``` function animalParty({ dog, cat }) {} const myDict = { dog: 'woof', cat: 'meow', }; animalParty(myDict); ``` 它也適用於陣列: 解構範例三 ``` [a, b] = [10, 20]; console.log(a); // prints 10 ``` --- 以上,希望對您有幫助!

快速複習 React 基本觀念&實務範例:推薦新手參考

React 作為一個強大的前端工具,有很多需要熟悉的基本觀念&語法。 這篇文章做一個快速的全面整理,方便工作時,可以隨時翻閱,希望您喜歡。 - 原文出處:https://dev.to/reedbarger/the-react-cheatsheet-for-2020-real-world-examples-4hgg ## 核心概念 ### 元素和 JSX - React 元素的基本語法 ``` // In a nutshell, JSX allows us to write HTML in our JS // JSX can use any valid html tags (i.e. div/span, h1-h6, form/input, etc) <div>Hello React</div> ``` - JSX 元素就是一段表達式 ``` // as an expression, JSX can be assigned to variables... const greeting = <div>Hello React</div>; const isNewToReact = true; // ... or can be displayed conditionally function sayGreeting() { if (isNewToReact) { // ... or returned from functions, etc. return greeting; // displays: Hello React } else { return <div>Hi again, React</div>; } } ``` - JSX 允許我們嵌套表達式 ``` const year = 2020; // we can insert primitive JS values in curly braces: {} const greeting = <div>Hello React in {year}</div>; // trying to insert objects will result in an error ``` - JSX 允許我們嵌套元素 ``` // to write JSX on multiple lines, wrap in parentheses: () const greeting = ( // div is the parent element <div> {/* h1 and p are child elements */} <h1>Hello!</h1> <p>Welcome to React</p> </div> ); // 'parents' and 'children' are how we describe JSX elements in relation // to one another, like we would talk about HTML elements ``` - HTML 和 JSX 的語法略有不同 ``` // Empty div is not <div></div> (HTML), but <div/> (JSX) <div/> // A single tag element like input is not <input> (HTML), but <input/> (JSX) <input name="email" /> // Attributes are written in camelcase for JSX (like JS variables <button className="submit-button">Submit</button> // not 'class' (HTML) ``` - 最基本的 React 應用程式需要三樣東西: - 1. ReactDOM.render() 渲染我們的應用程序 - 2. 一個 JSX 元素(在此情況下,稱為根節點) - 3. 一個用於掛載應用程式的 DOM 元素(通常是 index.html 文件中 id 為 root 的 div) ``` // imports needed if using NPM package; not if from CDN links import React from "react"; import ReactDOM from "react-dom"; const greeting = <h1>Hello React</h1>; // ReactDOM.render(root node, mounting point) ReactDOM.render(greeting, document.getElementById("root")); ``` ### 元件和 Props - 基本 React 元件的語法 ``` import React from "react"; // 1st component type: function component function Header() { // function components must be capitalized unlike normal JS functions // note the capitalized name here: 'Header' return <h1>Hello React</h1>; } // function components with arrow functions are also valid const Header = () => <h1>Hello React</h1>; // 2nd component type: class component // (classes are another type of function) class Header extends React.Component { // class components have more boilerplate (with extends and render method) render() { return <h1>Hello React</h1>; } } ``` - 如何使用元件 ``` // do we call these function components like normal functions? // No, to execute them and display the JSX they return... const Header = () => <h1>Hello React</h1>; // ...we use them as 'custom' JSX elements ReactDOM.render(<Header />, document.getElementById("root")); // renders: <h1>Hello React</h1> ``` - 元件可以在我們的應用程式中重複使用 ``` // for example, this Header component can be reused in any app page // this component shown for the '/' route function IndexPage() { return ( <div> <Header /> <Hero /> <Footer /> </div> ); } // shown for the '/about' route function AboutPage() { return ( <div> <Header /> <About /> <Testimonials /> <Footer /> </div> ); } ``` - 資料可以通過 props 動態傳遞給元件 ``` // What if we want to pass data to our component from a parent? // I.e. to pass a user's name to display in our Header? const username = "John"; // we add custom 'attributes' called props ReactDOM.render( <Header username={username} />, document.getElementById("root") ); // we called this prop 'username', but can use any valid JS identifier // props is the object that every component receives as an argument function Header(props) { // the props we make on the component (i.e. username) // become properties on the props object return <h1>Hello {props.username}</h1>; } ``` - Props 不可直接改變(mutate) ``` // Components must ideally be 'pure' functions. // That is, for every input, we be able to expect the same output // we cannot do the following with props: function Header(props) { // we cannot mutate the props object, we can only read from it props.username = "Doug"; return <h1>Hello {props.username}</h1>; } // But what if we want to modify a prop value that comes in? // That's where we would use state (see the useState section) ``` - 如果我們想將元素/元件作為 props 傳遞給其它元件,可以用 children props ``` // Can we accept React elements (or components) as props? // Yes, through a special property on the props object called 'children' function Layout(props) { return <div className="container">{props.children}</div>; } // The children prop is very useful for when you want the same // component (such as a Layout component) to wrap all other components: function IndexPage() { return ( <Layout> <Header /> <Hero /> <Footer /> </Layout> ); } // different page, but uses same Layout component (thanks to children prop) function AboutPage() { return ( <Layout> <About /> <Footer /> </Layout> ); } ``` - 可以用三元運算子來條件顯示元件 ``` // if-statements are fine to conditionally show , however... // ...only ternaries (seen below) allow us to insert these conditionals // in JSX, however function Header() { const isAuthenticated = checkAuth(); return ( <nav> <Logo /> {/* if isAuth is true, show AuthLinks. If false, Login */} {isAuthenticated ? <AuthLinks /> : <Login />} {/* if isAuth is true, show Greeting. If false, nothing. */} {isAuthenticated && <Greeting />} </nav> ); } ``` - Fragments 是用來顯示多個元件的特殊元件,無需向 DOM 添加額外的元素 - Fragments 適合用在條件邏輯 ``` // we can improve the logic in the previous example // if isAuthenticated is true, how do we display both AuthLinks and Greeting? function Header() { const isAuthenticated = checkAuth(); return ( <nav> <Logo /> {/* we can render both components with a fragment */} {/* fragments are very concise: <> </> */} {isAuthenticated ? ( <> <AuthLinks /> <Greeting /> </> ) : ( <Login /> )} </nav> ); } ``` ### 列表和 keys - 使用 .map() 將資料列表(陣列)轉換為元素列表 ``` const people = ["John", "Bob", "Fred"]; const peopleList = people.map(person => <p>{person}</p>); ``` - .map() 也可用來轉換為元件列表 ``` function App() { const people = ['John', 'Bob', 'Fred']; // can interpolate returned list of elements in {} return ( <ul> {/* we're passing each array element as props */} {people.map(person => <Person name={person} />} </ul> ); } function Person({ name }) { // gets 'name' prop using object destructuring return <p>this person's name is: {name}</p>; } ``` - 迭代的每個 React 元素都需要一個特殊的 `key` prop - key 對於 React 來說是必須的,以便能夠追蹤每個正在用 map 迭代的元素 - 沒有 key,當資料改變時,React 較難弄清楚元素應該如何更新 - key 應該是唯一值,才能讓 React 知道如何分辨 ``` function App() { const people = ['John', 'Bob', 'Fred']; return ( <ul> {/* keys need to be primitive values, ideally a generated id */} {people.map(person => <Person key={person} name={person} />)} </ul> ); } // If you don't have ids with your set of data or unique primitive values, // you can use the second parameter of .map() to get each elements index function App() { const people = ['John', 'Bob', 'Fred']; return ( <ul> {/* use array element index for key */} {people.map((person, i) => <Person key={i} name={person} />)} </ul> ); } ``` ### 事件和事件處理器 - React 和 HTML 中的事件略有不同 ``` // Note: most event handler functions start with 'handle' function handleToggleTheme() { // code to toggle app theme } // in html, onclick is all lowercase <button onclick="handleToggleTheme()"> Submit </button> // in JSX, onClick is camelcase, like attributes / props // we also pass a reference to the function with curly braces <button onClick={handleToggleTheme}> Submit </button> ``` - 最該先學的 React 事件是 onClick 和 onChange - onClick 處理 JSX 元素上的點擊事件(也就是按鈕動作) - onChange 處理鍵盤事件(也就是打字動作) ``` function App() { function handleChange(event) { // when passing the function to an event handler, like onChange // we get access to data about the event (an object) const inputText = event.target.value; const inputName = event.target.name; // myInput // we get the text typed in and other data from event.target } function handleSubmit() { // on click doesn't usually need event data } return ( <div> <input type="text" name="myInput" onChange={handleChange} /> <button onClick={handleSubmit}>Submit</button> </div> ); } ``` ## React Hooks ### State and useState - useState 為我們提供 function component 中的本地狀態 ``` import React from 'react'; // create state variable // syntax: const [stateVariable] = React.useState(defaultValue); function App() { const [language] = React.useState('javascript'); // we use array destructuring to declare state variable return <div>I am learning {language}</div>; } ``` - 注意:此段文章中的任何 hook 都來自 React 套件,且可以單獨導入 ``` import React, { useState } from "react"; function App() { const [language] = useState("javascript"); return <div>I am learning {language}</div>; } ``` - useState 還為我們提供了一個 `setter` 函數來更新它的狀態 ``` function App() { // the setter function is always the second destructured value const [language, setLanguage] = React.useState("python"); // the convention for the setter name is 'setStateVariable' return ( <div> {/* why use an arrow function here instead onClick={setterFn()} ? */} <button onClick={() => setLanguage("javascript")}> Change language to JS </button> {/* if not, setLanguage would be called immediately and not on click */} <p>I am now learning {language}</p> </div> ); } // note that whenever the setter function is called, the state updates, // and the App component re-renders to display the new state ``` - useState 可以在單個元件中使用一次或多次 ``` function App() { const [language, setLanguage] = React.useState("python"); const [yearsExperience, setYearsExperience] = React.useState(0); return ( <div> <button onClick={() => setLanguage("javascript")}> Change language to JS </button> <input type="number" value={yearsExperience} onChange={event => setYearsExperience(event.target.value)} /> <p>I am now learning {language}</p> <p>I have {yearsExperience} years of experience</p> </div> ); } ``` - useState 可以接受 primitive value 或物件 ``` // we have the option to organize state using whatever is the // most appropriate data type, according to the data we're tracking function App() { const [developer, setDeveloper] = React.useState({ language: "", yearsExperience: 0 }); function handleChangeYearsExperience(event) { const years = event.target.value; // we must pass in the previous state object we had with the spread operator setDeveloper({ ...developer, yearsExperience: years }); } return ( <div> {/* no need to get prev state here; we are replacing the entire object */} <button onClick={() => setDeveloper({ language: "javascript", yearsExperience: 0 }) } > Change language to JS </button> {/* we can also pass a reference to the function */} <input type="number" value={developer.yearsExperience} onChange={handleChangeYearsExperience} /> <p>I am now learning {developer.language}</p> <p>I have {developer.yearsExperience} years of experience</p> </div> ); } ``` - 如果新狀態依賴於之前的狀態,我們可以在 setter 函數中使用一個函數來為我們提供之前狀態的值 ``` function App() { const [developer, setDeveloper] = React.useState({ language: "", yearsExperience: 0, isEmployed: false }); function handleToggleEmployment(event) { // we get the previous state variable's value in the parameters // we can name 'prevState' however we like setDeveloper(prevState => { return { ...prevState, isEmployed: !prevState.isEmployed }; // it is essential to return the new state from this function }); } return ( <button onClick={handleToggleEmployment}>Toggle Employment Status</button> ); } ``` ### side effects 和 useEffect - useEffect 讓我們在 function component 中執行副作用。什麼是副作用? - 副作用是我們需要接觸外部世界的地方。例如,從 API 獲取資料或操作 DOM。 - 副作用是可能以不可預測的方式,改變我們元件狀態的操作(導致「副作用」)。 - useEffect 接受 callback function(稱為 effect 函數),預設情況下,每次重新渲染時都會運行 - useEffect 在我們的元件載入後運行,可以準確在元件的各個生命週期觸發 ``` // what does our code do? Picks a color from the colors array // and makes it the background color function App() { const [colorIndex, setColorIndex] = React.useState(0); const colors = ["blue", "green", "red", "orange"]; // we are performing a 'side effect' since we are working with an API // we are working with the DOM, a browser API outside of React useEffect(() => { document.body.style.backgroundColor = colors[colorIndex]; }); // whenever state is updated, App re-renders and useEffect runs function handleChangeIndex() { const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1; setColorIndex(next); } return <button onClick={handleChangeIndex}>Change background color</button>; } ``` - 如果不想在每次渲染後都執行 callback function,可以在第二個參數傳一個空陣列 ``` function App() { ... // now our button doesn't work no matter how many times we click it... useEffect(() => { document.body.style.backgroundColor = colors[colorIndex]; }, []); // the background color is only set once, upon mount // how do we not have the effect function run for every state update... // but still have it work whenever the button is clicked? return ( <button onClick={handleChangeIndex}> Change background color </button> ); } ``` - useEffect 讓我們能夠在 dependencies 陣列的內容改變時,才執行 - dependencies 陣列是第二個參數,如果陣列中的任何一個值發生變化,effect function 將再次運行 ``` function App() { const [colorIndex, setColorIndex] = React.useState(0); const colors = ["blue", "green", "red", "orange"]; // we add colorIndex to our dependencies array // when colorIndex changes, useEffect will execute the effect fn again useEffect(() => { document.body.style.backgroundColor = colors[colorIndex]; // when we use useEffect, we must think about what state values // we want our side effect to sync with }, [colorIndex]); function handleChangeIndex() { const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1; setColorIndex(next); } return <button onClick={handleChangeIndex}>Change background color</button>; } ``` - 可以在 useEffect 最後回傳一個函數,來取消訂閱某些效果 ``` function MouseTracker() { const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); React.useEffect(() => { // .addEventListener() sets up an active listener... window.addEventListener("mousemove", handleMouseMove); // ...so when we navigate away from this page, it needs to be // removed to stop listening. Otherwise, it will try to set // state in a component that doesn't exist (causing an error) // We unsubscribe any subscriptions / listeners w/ this 'cleanup function' return () => { window.removeEventListener("mousemove", handleMouseMove); }; }, []); function handleMouseMove(event) { setMousePosition({ x: event.pageX, y: event.pageY }); } return ( <div> <h1>The current mouse position is:</h1> <p> X: {mousePosition.x}, Y: {mousePosition.y} </p> </div> ); } // Note: we could extract the reused logic in the callbacks to // their own function, but I believe this is more readable ``` - 使用 useEffect 撈取資料 - 請注意,如果要使用更簡潔的 async/awit 語法處理 promise,則需要建立一個單獨的函數(因為 effect callback function 不能是 async) ``` const endpoint = "https://api.github.com/users/codeartistryio"; // with promises: function App() { const [user, setUser] = React.useState(null); React.useEffect(() => { // promises work in callback fetch(endpoint) .then(response => response.json()) .then(data => setUser(data)); }, []); } // with async / await syntax for promise: function App() { const [user, setUser] = React.useState(null); // cannot make useEffect callback function async React.useEffect(() => { getUser(); }, []); // instead, use async / await in separate function, then call // function back in useEffect async function getUser() { const response = await fetch("https://api.github.com/codeartistryio"); const data = await response.json(); setUser(data); } } ``` ### 效能和 useCallback - useCallback 是一個用來提高元件性能的 hook - 如果你有一個經常重新渲染的元件,useCallback 可以防止元件內的 callback function 在每次元件重新渲染時都重新創建(導致元件重新運行) - useCallback 僅在其依賴項之一改變時重新運行 ``` // in Timer, we are calculating the date and putting it in state a lot // this results in a re-render for every state update // we had a function handleIncrementCount to increment the state 'count'... function Timer() { const [time, setTime] = React.useState(); const [count, setCount] = React.useState(0); // ... but unless we wrap it in useCallback, the function is // recreated for every single re-render (bad performance hit) // useCallback hook returns a callback that isn't recreated every time const inc = React.useCallback( function handleIncrementCount() { setCount(prevCount => prevCount + 1); }, // useCallback accepts a second arg of a dependencies array like useEffect // useCallback will only run if any dependency changes (here it's 'setCount') [setCount] ); React.useEffect(() => { const timeout = setTimeout(() => { const currentTime = JSON.stringify(new Date(Date.now())); setTime(currentTime); }, 300); return () => { clearTimeout(timeout); }; }, [time]); return ( <div> <p>The current time is: {time}</p> <p>Count: {count}</p> <button onClick={inc}>+</button> </div> ); } ``` ### Memoization 和 useMemo - useMemo 和 useCallback 非常相似,都是為了提高效能。但就不是為了暫存 callback function,而是為了暫存耗費效能的運算結果 - useMemo 允許我們「記憶」已經為某些輸入進行了昂貴計算的結果(之後就不用重新計算了) - useMemo 從計算中回傳一個值,而不是 callback 函數(但可以是一個函數) ``` // useMemo is useful when we need a lot of computing resources // to perform an operation, but don't want to repeat it on each re-render function App() { // state to select a word in 'words' array below const [wordIndex, setWordIndex] = useState(0); // state for counter const [count, setCount] = useState(0); // words we'll use to calculate letter count const words = ["i", "am", "learning", "react"]; const word = words[wordIndex]; function getLetterCount(word) { // we mimic expensive calculation with a very long (unnecessary) loop let i = 0; while (i < 1000000) i++; return word.length; } // Memoize expensive function to return previous value if input was the same // only perform calculation if new word without a cached value const letterCount = React.useMemo(() => getLetterCount(word), [word]); // if calculation was done without useMemo, like so: // const letterCount = getLetterCount(word); // there would be a delay in updating the counter // we would have to wait for the expensive function to finish function handleChangeIndex() { // flip from one word in the array to the next const next = wordIndex + 1 === words.length ? 0 : wordIndex + 1; setWordIndex(next); } return ( <div> <p> {word} has {letterCount} letters </p> <button onClick={handleChangeIndex}>Next word</button> <p>Counter: {count}</p> <button onClick={() => setCount(count + 1)}>+</button> </div> ); } ``` ### Refs 和 useRef - Refs 是所有 React 組件都可用的特殊屬性。允許我們在元件安裝時,創建針對特定元素/元件的引用 - useRef 允許我們輕鬆使用 React refs - 我們在元件開頭呼叫 useRef,並將回傳值附加到元素的 ref 屬性來引用它 - 一旦我們創建了一個引用,就可以用它來改變元素的屬性,或者可以呼叫該元素上的任何可用方法(比如用 .focus() 來聚焦) ``` function App() { const [query, setQuery] = React.useState("react hooks"); // we can pass useRef a default value // we don't need it here, so we pass in null to ref an empty object const searchInput = useRef(null); function handleClearSearch() { // current references the text input once App mounts searchInput.current.value = ""; // useRef can store basically any value in its .current property searchInput.current.focus(); } return ( <form> <input type="text" onChange={event => setQuery(event.target.value)} ref={searchInput} /> <button type="submit">Search</button> <button type="button" onClick={handleClearSearch}> Clear </button> </form> ); } ``` ## 進階 Hook ### Context 和 useContext - 在 React 中,盡量避免創建多個 props 來將資料從父元件向下傳遞兩個或多個層級 ``` // Context helps us avoid creating multiple duplicate props // This pattern is also called props drilling: function App() { // we want to pass user data down to Header const [user] = React.useState({ name: "Fred" }); return ( // first 'user' prop <Main user={user} /> ); } const Main = ({ user }) => ( <> {/* second 'user' prop */} <Header user={user} /> <div>Main app content...</div> </> ); const Header = ({ user }) => <header>Welcome, {user.name}!</header>; ``` - Context 有助於將 props 從父組件向下傳遞到多個層級的子元件 ``` // Here is the previous example rewritten with Context // First we create context, where we can pass in default values const UserContext = React.createContext(); // we call this 'UserContext' because that's what data we're passing down function App() { // we want to pass user data down to Header const [user] = React.useState({ name: "Fred" }); return ( {/* we wrap the parent component with the provider property */} {/* we pass data down the computer tree w/ value prop */} <UserContext.Provider value={user}> <Main /> </UserContext.Provider> ); } const Main = () => ( <> <Header /> <div>Main app content...</div> </> ); // we can remove the two 'user' props, we can just use consumer // to consume the data where we need it const Header = () => ( {/* we use this pattern called render props to get access to the data*/} <UserContext.Consumer> {user => <header>Welcome, {user.name}!</header>} </UserContext.Consumer> ); ``` - useContext hook 可以移除這種看起來很怪的 props 渲染模式,同時又能在各種 function component 中隨意使用 ``` const Header = () => { // we pass in the entire context object to consume it const user = React.useContext(UserContext); // and we can remove the Consumer tags return <header>Welcome, {user.name}!</header>; }; ``` ### Reducers 和 useReducer - Reducers 是簡單、可預測的(純)函數,它接受一個先前的狀態物件和一個動作物件,並回傳一個新的狀態物件。例如: ``` // let's say this reducer manages user state in our app: function reducer(state, action) { // reducers often use a switch statement to update state // in one way or another based on the action's type property switch (action.type) { // if action.type has the string 'LOGIN' on it case "LOGIN": // we get data from the payload object on action return { username: action.payload.username, isAuth: true }; case "SIGNOUT": return { username: "", isAuth: false }; default: // if no case matches, return previous state return state; } } ``` - Reducers 是一種強大的狀態管理模式,用於流行的 Redux 狀態管理套件(這套很常與 React 一起使用) - 與 useState(用於本地元件狀態)相比,Reducers 可以通過 useReducer hook 在 React 中使用,以便管理我們應用程序的狀態 - useReducer 可以與 useContext 配對來管理資料並輕鬆地將資料傳遞給元件 - useReducer + useContext 可以當成應用程式的整套狀態管理系統 ``` const initialState = { username: "", isAuth: false }; function reducer(state, action) { switch (action.type) { case "LOGIN": return { username: action.payload.username, isAuth: true }; case "SIGNOUT": // could also spread in initialState here return { username: "", isAuth: false }; default: return state; } } function App() { // useReducer requires a reducer function to use and an initialState const [state, dispatch] = useReducer(reducer, initialState); // we get the current result of the reducer on 'state' // we use dispatch to 'dispatch' actions, to run our reducer // with the data it needs (the action object) function handleLogin() { dispatch({ type: "LOGIN", payload: { username: "Ted" } }); } function handleSignout() { dispatch({ type: "SIGNOUT" }); } return ( <> Current user: {state.username}, isAuthenticated: {state.isAuth} <button onClick={handleLogin}>Login</button> <button onClick={handleSignout}>Signout</button> </> ); } ``` ### 編寫 custom hooks - 創建 hook 就能輕鬆在元件之間重用某些行為 - hook 是一種比以前的 class component 更容易理解的模式,例如 higher-order components 或者 render props - 根據需要,隨時可以自創一些 hook ``` // here's a custom hook that is used to fetch data from an API function useAPI(endpoint) { const [value, setValue] = React.useState([]); React.useEffect(() => { getData(); }, []); async function getData() { const response = await fetch(endpoint); const data = await response.json(); setValue(data); }; return value; }; // this is a working example! try it yourself (i.e. in codesandbox.io) function App() { const todos = useAPI("https://todos-dsequjaojf.now.sh/todos"); return ( <ul> {todos.map(todo => <li key={todo.id}>{todo.text}</li>)} </ul> ); } ``` ### Hooks 規則 - 使用 React hooks 有兩個核心規則,要遵守才能正常運作: - 1. Hooks 只能在元件開頭呼叫 - Hooks 不能放在條件、迴圈或嵌套函數中 - 2. Hooks只能在 function component 內部使用 - Hooks 不能放在普通的 JavaScript 函數或 class component 中 ``` function checkAuth() { // Rule 2 Violated! Hooks cannot be used in normal functions, only components React.useEffect(() => { getUser(); }, []); } function App() { // this is the only validly executed hook in this component const [user, setUser] = React.useState(null); // Rule 1 violated! Hooks cannot be used within conditionals (or loops) if (!user) { React.useEffect(() => { setUser({ isAuth: false }); // if you want to conditionally execute an effect, use the // dependencies array for useEffect }, []); } checkAuth(); // Rule 1 violated! Hooks cannot be used in nested functions return <div onClick={() => React.useMemo(() => doStuff(), [])}>Our app</div>; } ``` --- 以上是快速整理,希望對您有幫助!

如何在 React 中漂亮地 render 一個列表:四點注意事項

面試的時候,常常會被問到這題。看似簡單,其實有一些進階注意事項,此篇與您分享。 - 原文出處:https://dev.to/andyrewlee/stand-out-in-a-react-interview-by-rendering-a-list-like-a-pro-1cn5 # 基本做法 以下是最基本做法。根據陣列內容,直接渲染成一個列表。 ``` import { useState, useEffect } from "react"; const App = () => { const [posts, setPosts] = useState([]); const [currentPost, setCurrentPost] = useState(undefined); useEffect(() => { const initialize = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const json = await res.json(); setPosts(json); }; initialize(); }, []); const onPostClick = (post) => { setCurrentPost(post); }; return ( <div> {currentPost && <h1>{currentPost.title}</h1>} <PostList posts={posts} onPostClick={onPostClick} /> </div> ); }; const PostList = ({ posts, onPostClick }) => { return ( <div> {posts.map((post) => ( <Post post={post} onPostClick={onPostClick} /> ))} </div> ); }; const Post = ({ post, onPostClick }) => { const onClick = () => { onPostClick(post); }; return <div onClick={onClick}>{post.title}</div>; }; export default App; ``` # 改進方法 以下是四個可以改進的方向,以及背後的原因。 ## 1. 指定 key 替每個元件提供一個 key 屬性,可以幫助後續 React 渲染時改善效能。要注意 key 屬性要是唯一值,不要直接用陣列索引當成 key。 ``` {posts.map((post) => ( <Post key={post.id} post={post} onPostClick={onPostClick} /> ))} ``` ## 2. 優化渲染 每次點擊列表項目時,都會重新渲染 `PostList` 和每個 `Post`。 ``` const Post = ({ post, onPostClick }) => { console.log("post rendered"); const onClick = () => { onPostClick(post); }; return <div onClick={onClick}>{post}</div>; }; ``` 可以使用 React 提供的 `memo` 功能來優化 `PostList` 元件。用 `memo` 包裝元件時,等於告訴 React:除非 props 改變,否則不要重新渲染這個元件。 ``` import { useState, useEffect, memo } from "react"; const PostList = memo(({ posts, onPostClick }) => { return ( <div> {posts.map((post) => ( <Post post={post} onPostClick={onPostClick} /> ))} </div> ); }); ``` 不過,實際跑下去,會發現還是重新渲染了。因為每次 currentPost 改變時,App 都會重新渲染。每次重新渲染都會重新建立 onPostClick 函數。當一個函數被重新建立時(即使函式內容一樣),都算是新的實體。所以技術上來說,props 確實發生了變化,所以 `PostList` 會重新渲染。 ``` const fn1 = () => {}; const fn2 = () => {}; fn1 === fn2; // => false ``` 這種狀況,就可以使用 useCallback hook 來告訴 React 不要重新建立函數。 ``` const onPostClick = useCallback((post) => { setCurrentPost(post); }, []); ``` 上面的例子中,使用 useCallback 沒問題,可以避免全部貼文被重新渲染。但要知道的是,並非什麼函式都適合包在 useCallback 裡面。 ``` const Post = ({ post, onPostClick }) => { const useCalllback(onClick = () => { onPostClick(post); }, []); return <div onClick={onClick}>{post.title}</div>; }; ``` 比方說,在 Post 元件使用 useCallback 就不太有意義。因為這個元件是輕量級的。應該只在有意義的情況下使用 useCallback(可以用 profiling 工具分析效能確認)。`useCallback` 有缺點:它增加了程式碼的複雜性。使用 useCallback 會在每次渲染時運行的額外程式碼。 ## 3. 元件卸載時清理乾淨 目前的元件沒有在卸載時進行清理。例如,如果在收到 URL 回應結果之前就離開頁面怎麼辦?這時應該要取消請求。 ``` useEffect(() => { const initialize = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const json = await res.json(); setPosts(json); }; initialize(); }, []); ``` `useEffect` 可以分為兩部分:掛載時運行的程式碼、卸載時運行的程式碼: ``` useEffect(() => { // When component mounts what code should I run? return () => { // When component unmounts what code should I run (clean up)? }; }, []); ``` 我們可以使用 AbortController ,並在清理時呼叫 controller.abort() 來取消請求。 ``` useEffect(() => { const controller = new AbortController(); const signal = controller.signal; const initialize = async () => { try { const res = await fetch("https://jsonplaceholder.typicode.com/posts", { signal }); const json = await res.json(); setPosts(json); } catch (err) { console.log(err); } }; initialize(); return () => { controller.abort(); }; }, []); ``` ## 4. 增加無障礙輔助功能 以上範例太過簡單,沒有太多無障礙功能可以加入。一旦應用程式變複雜,絕對應該要開始考慮這點。 有個簡單的無障礙測試:可以單獨只使用鍵盤就操作一個應用程式嗎? 關於這點,快速解法是:把每個項目都轉成按鈕,用鍵盤時就可以在元件之間切換。 ``` const Post = ({ post, onPostClick }) => { const onClick = () => { onPostClick(post); }; return <button onClick={onClick}>{post}</button>; }; ``` # 結論 在 React 中渲染列表,乍看之下,只是個簡單的面試問題。但其實可深可淺。下次面試時遇到這問題,可以思考一下此篇文章談到的各個面向。

20 個一行 JavaScript 就能完成的小任務

一行程式碼可以做到很多事。這邊有20個小任務都用一行就可以完成,給您參考! - 原文出處:https://dev.to/saviomartin/20-killer-javascript-one-liners-94f # 獲取瀏覽器 Cookie 的值 讀取 `document.cookie` 來查 cookie 的值 ``` const cookie = name => `; ${document.cookie}`.split(`; ${name}=`).pop().split(';').shift(); cookie('_ga'); // Result: "GA1.2.1929736587.1601974046" ``` # 將 RGB 轉換為十六進制 ``` const rgbToHex = (r, g, b) => "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); rgbToHex(0, 51, 255); // Result: #0033ff ``` # 複製到剪貼板 使用 `navigator.clipboard.writeText` 輕鬆將任何文字複製到剪貼板。 ``` const copyToClipboard = (text) => navigator.clipboard.writeText(text); copyToClipboard("Hello World"); ``` # 檢查日期是否有效 使用以下程式碼檢查給定日期是否有效。 ``` const isDateValid = (...val) => !Number.isNaN(new Date(...val).valueOf()); isDateValid("December 17, 1995 03:24:00"); // Result: true ``` # 查找一年中的第幾天 根據給定日期,找出是第幾天。 ``` const dayOfYear = (date) => Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24); dayOfYear(new Date()); // Result: 272 ``` # 將字串開頭大寫 Javascript 沒有內建的 capitalize 函數。我們可以使用以下程式碼來完成。 ``` const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1) capitalize("follow for more") // Result: Follow for more ``` # 求兩天之間的天數 使用以下程式碼查找 2 個給定日期之間的天數。 ``` const dayDif = (date1, date2) => Math.ceil(Math.abs(date1.getTime() - date2.getTime()) / 86400000) dayDif(new Date("2020-10-21"), new Date("2021-10-22")) // Result: 366 ``` # 清除所有 Cookie 透過 document.cookie 存取 cookie 並清除它,就可輕鬆清除網頁中的所有 cookie。 ``` const clearCookies = document.cookie.split(';').forEach(cookie => document.cookie = cookie.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`)); ``` # 生成隨機十六進制顏色碼 使用“Math.random”和“padEnd”屬性,生成隨機的十六進制顏色碼。 ``` const randomHex = () => `#${Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0")}`; console.log(randomHex()); // Result: #92b008 ``` # 從陣列中刪除重複項 使用 JavaScript 中的 `Set` 輕鬆刪除重複項。 ``` const removeDuplicates = (arr) => [...new Set(arr)]; console.log(removeDuplicates([1, 2, 3, 3, 4, 4, 5, 5, 6])); // Result: [ 1, 2, 3, 4, 5, 6 ] ``` # 從 URL 獲取查詢參數 您可以從 `window.location` 或原始 URL `goole.com?search=easy&page=3` 中輕鬆找出查詢參數 ``` const getParameters = (URL) => { URL = JSON.parse('{"' + decodeURI(URL.split("?")[1]).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') +'"}'); return JSON.stringify(URL); }; getParameters(window.location) // Result: { search : "easy", page : 3 } ``` # 把日期物件轉成時間 把日期物件以“hour::minutes::seconds”格式轉成時間。 ``` const timeFromDate = date => date.toTimeString().slice(0, 8); console.log(timeFromDate(new Date(2021, 0, 10, 17, 30, 0))); // Result: "17:30:00" ``` # 檢查數字是偶數還是奇數 ``` const isEven = num => num % 2 === 0; console.log(isEven(2)); // Result: True ``` # 求數的平均值 使用 `reduce` 方法計算多個數字之間的平均值。 ``` const average = (...args) => args.reduce((a, b) => a + b) / args.length; average(1, 2, 3, 4); // Result: 2.5 ``` # 滾動到頂部 您可以使用 `window.scrollTo(0, 0)` 方法自動滾動到頂部。將 `x` 和 `y` 都設為 0。 ``` const goToTop = () => window.scrollTo(0, 0); goToTop(); ``` # 反轉字串 您可以使用 `split`、`reverse` 和 `join` 方法輕鬆反轉字串。 ``` const reverse = str => str.split('').reverse().join(''); reverse('hello world'); // Result: 'dlrow olleh' ``` # 檢查陣列是否為空 ``` const isNotEmpty = arr => Array.isArray(arr) && arr.length > 0; isNotEmpty([1, 2, 3]); // Result: true ``` # 取得選中的文字 使用內建的 getSelection 屬性取得用戶選取中的文字。 ``` const getSelectedText = () => window.getSelection().toString(); getSelectedText(); ``` # 打亂陣列 使用 `sort` 和 `random` 方法打亂陣列。 ``` const shuffleArray = (arr) => arr.sort(() => 0.5 - Math.random()); console.log(shuffleArray([1, 2, 3, 4])); // Result: [ 1, 4, 3, 2 ] ``` # 檢測深色模式 使用以下程式碼,檢查用戶的設備是否處於深色模式。 ``` const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches console.log(isDarkMode) // Result: True or False ``` --- 希望這些程式碼,有給您一些靈感!

在 JavaScript 新版 ES2020 之中,值得留意的 10 個新功能

ES2020 已經上線一段時間了,但是滿多新功能都大家不太知道。這篇文章跟大家介紹一下! - 原文出處:https://dev.to/worldindev/10-new-javascript-features-in-es2020-that-you-should-know-3ohf # 1. BigInt BigInt 是最受期待的新功能之一,允許工程師處理資料時,能存一個更大的整數。 目前在 JavaScript 中可以儲存的最大整數是 `pow(2, 53) - 1`。BigInt 讓你可以存更大的數字。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/588khrk3rpei76gho4e9.png) 如上所示,需要在後面加上一個 `n`。這個 `n` 表示這是一個 BigInt ,讓 JavaScript 引擎(v8 引擎)能夠特別處理。 這個功能不向後兼容,因為傳統的數字系統是 IEEE754(不能支援這種大小的數字)。 # 2.Dynamic import 動態導入讓你可在程式碼中,有條件地動態導入模組。跟你現在用 Webpack 和 Babel 做的事一樣。 此功能讓你可以按需求讀取程式碼,也就是所謂的 code splitting。讓你不再需要 webpack 或其他模組打包工具就能做到。您還可以在 if-else 中有條件地載入程式碼。這有個額外好處,就是不會污染全域命名空間。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utai4m6xcc1nk3aooqxf.png) # 3. Nullish Coalescing Nullish Coalescing 可以確實檢查 nullish 而不是 falsy 值。什麼是 nullish 與 falsy 值? 在 JavaScript 中,許多值都是 `falsy`,例如空字串、數字 0、`undefined`、`null`、`false`、`NaN` 等等。 但有時候你想要檢查 undefined 跟 null。這種情況下,就可以用新的運算子 `??` ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uupnryjugtdtb6xvv7yk.png) 如圖, **OR** 運算子始終回傳 truthy 值,而 ?? 運算子回傳 non-nullish 值。 # 4. Optional Chaining Optional Chaining 語法讓你可以取得深度嵌套的物件屬性,而不用擔心屬性不存在。如果找不到屬性,會回傳 undefined,而不會報錯壞掉。物件屬性可用,呼叫函數跟陣列也可以用。超方便!請參考: ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4w5zkv64r0cs46xpjie.png) # 5. Promise.allSettled `Promise.allSettled` 方法接受一個 Promise 陣列,並且只有在所有 Promise 都完成時才 resolve 或 reject。 以前只能用 `race` 和 `all` 做出類似效果。 ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xi1hpkcazxgxsl81d90.png) # 6. String#matchAll `matchAll` 是添加到 `String` 原型的新方法,與正則表達式相關。這將回傳一個迭代器,該迭代器依次返回所有匹配的組。舉例: ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmmk478r36d1bfjovi1v.png) # 7. globalThis 如果你是寫跨平台的 JS 程式碼,可以在 Node 上運行、在瀏覽器運行、也可以在 web-workers 中運行,你很難操作全域物件。 因為對於瀏覽器來說它是 window ,對於 Node 來說是 global ,對於 web-workers 來說是 self。如果有其它的運行環境,它們的全局物件也都不同。 變成你需要自己寫一段來檢測環境。所以 ES2020 實作了 globalThis,無論您在哪裡執行,它始終引用全域物件: ![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5dczfi8moefwl4y1jo6.png) # 8. 模組命名空間匯出 在 JavaScript 模組中,可以使用以下語法: ``` import * as utils from './utils.mjs' ``` ES2020 新增了相對的匯出語法: ``` export * as utils from './utils.mjs' ``` 這等同於以下內容: ``` import * as utils from './utils.mjs' export { utils } ``` # 9. 定義好 for-in 順序 雖然大部份瀏覽器都實做了一樣的順序,但 ECMA 規範其實沒有指定 for (x in y) 的運行順序。在 ES2020 中正式標準化了。 # 10. import.meta `import.meta` 物件帶有一個 `null` 原型。 考慮以下模組,`module.js`: ``` <script type="module" src="module.js"></script> ``` 您可以使用 import.meta 物件取得有關模組的元信息: ``` console.log(import.meta); // { url: "file:///home/user/module.js" } ``` 對於 external scripts,它代表存取腳本的 URL,對於 inline scripts,它代表檔案的 URL。 --- 以上簡單分享,希望對你有幫助!

在 JavaScript 中使用 Regex 正規表示式:懶人 Cheat Sheet 備忘單

需要字串比對、搜尋等等功能時,regex 正規表示式非常好用。以下是一份 regex 的備忘單,方便需要時可以翻閱。 - 原文出處:https://dev.to/emmabostian/regex-cheat-sheet-2j2a --- **測試正規表示式** - 使用 `.test()` 方法 ``` let testString = "My test string"; let testRegex = /string/; testRegex.test(testString); ``` **測試多種模式** - 使用 OR 運算子 (|) ``` const regex = /yes|no|maybe/; ``` **忽略大小寫** - 使用 `i` 標誌不區分大小寫 ``` const caseInsensitiveRegex = /ignore case/i; const testString = 'We use the i flag to iGnOrE CasE'; caseInsensitiveRegex.test(testString); // true ``` **將第一個符合項提取到變數** - 使用 `.match()` 函數 ``` const match = "Hello World!".match(/hello/i); // "Hello" ``` **提取所有符合項到陣列中** - 使用`g`標誌 ``` const testString = "Repeat repeat rePeAT"; const regexWithAllMatches = /Repeat/gi; testString.match(regexWithAllMatches); // ["Repeat", "repeat", "rePeAT"] ``` **匹配任意字元** - 使用通配字元`.`作為任何字元的佔位符 ``` // To match "cat", "BAT", "fAT", "mat" const regexWithWildcard = /.at/gi; const testString = "cat BAT cupcake fAT mat dog"; const allMatchingWords = testString.match(regexWithWildcard); // ["cat", "BAT", "fAT", "mat"] ``` **用多種可能性匹配單個字元** - 定義一組您希望匹配的字元 - 放在中括號內即可`[]` ``` // Match "cat" "fat" and "mat" but not "bat" const regexWithCharClass = /[cfm]at/g; const testString = "cat fat bat mat"; const allMatchingWords = testString.match(regexWithCharClass); // ["cat", "fat", "mat"] ``` **匹配字母表中的字母** - 使用字元集合 `[a-z]` ``` const regexWithCharRange = /[a-e]at/; const catString = "cat"; const batString = "bat"; const fatString = "fat"; regexWithCharRange.test(catString); // true regexWithCharRange.test(batString); // true regexWithCharRange.test(fatString); // false ``` **匹配特定的數字和字母** - 還可以使用連字符來匹配數字 ``` const regexWithLetterAndNumberRange = /[a-z0-9]/ig; const testString = "Emma19382"; testString.match(regexWithLetterAndNumberRange) // true ``` **匹配一個未知字元** - 要匹配您*不*想要的一組字元,請使用否定字元集 - 使用插入符號 `^` 即可 ``` const allCharsNotVowels = /[^aeiou]/gi; const allCharsNotVowelsOrNumbers = /[^aeiou0-9]/gi; ``` **匹配連續出現一次或多次的字元** - 使用`+`符號 ``` const oneOrMoreAsRegex = /a+/gi; const oneOrMoreSsRegex = /s+/gi; const cityInFlorida = "Tallahassee"; cityInFlorida.match(oneOrMoreAsRegex); // ['a', 'a', 'a']; cityInFlorida.match(oneOrMoreSsRegex); // ['ss']; ``` **匹配連續出現零次或多次的字元** - 使用星號`*` ``` const zeroOrMoreOsRegex = /hi*/gi; const normalHi = "hi"; const happyHi = "hiiiiii"; const twoHis = "hiihii"; const bye = "bye"; normalHi.match(zeroOrMoreOsRegex); // ["hi"] happyHi.match(zeroOrMoreOsRegex); // ["hiiiiii"] twoHis.match(zeroOrMoreOsRegex); // ["hii", "hii"] bye.match(zeroOrMoreOsRegex); // null ``` **惰性匹配** - 符合要求的字串的最短部分 - 預設情況下,正則表達式是貪婪匹配(滿足要求的字串的最長部分) - 使用 `?` 字元進行惰性匹配 ``` const testString = "catastrophe"; const greedyRexex = /c[a-z]*t/gi; const lazyRegex = /c[a-z]*?t/gi; testString.match(greedyRexex); // ["catast"] testString.match(lazyRegex); // ["cat"] ``` **匹配起始字串** - 要測試字串開頭的字元,請使用插入符 `^`,但要在字元集之外 ``` const emmaAtFrontOfString = "Emma likes cats a lot."; const emmaNotAtFrontOfString = "The cats Emma likes are fluffy."; const startingStringRegex = /^Emma/; startingStringRegex.test(emmaAtFrontOfString); // true startingStringRegex.test(emmaNotAtFrontOfString); // false ``` **匹配結尾字串** - 在正則表達式末尾使用美元符號 `$` 來檢查字串末尾是否匹配 ``` const emmaAtBackOfString = "The cats do not like Emma"; const emmaNotAtBackOfString = "Emma loves the cats"; const startingStringRegex = /Emma$/; startingStringRegex.test(emmaAtBackOfString); // true startingStringRegex.test(emmaNotAtBackOfString); // false ``` **匹配所有字母和數字** - 使用 `\w` 簡寫 ``` const longHand = /[A-Za-z0-9_]+/; const shortHand = /\w+/; const numbers = "42"; const myFavoriteColor = "magenta"; longHand.test(numbers); // true shortHand.test(numbers); // true longHand.test(myFavoriteColor); // true shortHand.test(myFavoriteColor); // true ``` **匹配除字母和數字以外的所有內容** - 你可以使用 `\w` 的反義詞也就是 `\W` ``` const noAlphaNumericCharRegex = /\W/gi; const weirdCharacters = "!_$!!"; const alphaNumericCharacters = "ab283AD"; noAlphaNumericCharRegex.test(weirdCharacters); // true noAlphaNumericCharRegex.test(alphaNumericCharacters); // false ``` **匹配所有數字** - 你可以使用字元集 `[0-9]`,或者使用簡寫形式 `\d` ``` const digitsRegex = /\d/g; const stringWithDigits = "My cat eats $20.00 worth of food a week."; stringWithDigits.match(digitsRegex); // ["2", "0", "0", "0"] ``` **匹配所有非數字** - 你可以使用 `\d` 的反義詞也就是 `\D` ``` const nonDigitsRegex = /\D/g; const stringWithLetters = "101 degrees"; stringWithLetters.match(nonDigitsRegex); // [" ", "d", "e", "g", "r", "e", "e", "s"] ``` **匹配空格** - 使用 `\s` 匹配空格和換行 ``` const sentenceWithWhitespace = "I like cats!" var spaceRegex = /\s/g; whiteSpace.match(sentenceWithWhitespace); // [" ", " "] ``` **匹配非空格** - 你可以使用 `\s` 的反義詞也就是 `\S` ``` const sentenceWithWhitespace = "C a t" const nonWhiteSpaceRegex = /\S/g; sentenceWithWhitespace.match(nonWhiteSpaceRegex); // ["C", "a", "t"] ``` **匹配字元數** - 您可以使用 `{lowerBound, upperBound}` 在一行中指定特定數量的字元 ``` const regularHi = "hi"; const mediocreHi = "hiii"; const superExcitedHey = "heeeeyyyyy!!!"; const excitedRegex = /hi{1,4}/; excitedRegex.test(regularHi); // true excitedRegex.test(mediocreHi); // true excitedRegex.test(superExcitedHey); //false ``` **匹配最少的字元數** - 您可以使用 `{lowerBound,}` 要求滿足最少數量的字元 - 這稱為數量說明符 ``` const regularHi = "hi"; const mediocreHi = "hiii"; const superExcitedHey = "heeeeyyyyy!!!"; const excitedRegex = /hi{2,}/; excitedRegex.test(regularHi); // false excitedRegex.test(mediocreHi); // true excitedRegex.test(superExcitedHey); //false ``` **匹配準確的字元數** - 您可以使用 `{requiredCount}` 指定要求字元數 ``` const regularHi = "hi"; const bestHi = "hii"; const mediocreHi = "hiii"; const excitedRegex = /hi{2}/; excitedRegex.test(regularHi); // false excitedRegex.test(bestHi); // true excitedRegex.test(mediocreHi); //false ``` **某字元可以不出現** - 要允許某字元可以不出現,請使用 `?` ``` const britishSpelling = "colour"; const americanSpelling = "Color"; const languageRegex = /colou?r/i; languageRegex.test(britishSpelling); // true languageRegex.test(americanSpelling); // true ``` --- 以上是基礎用法&範例,方便工作時可以查閱!

關於 JavaScript 開發的 8 個少見、但還不錯用小技巧

JavaScipt 是非常流行的程式語言,其中有些少見技巧,但還不錯用,這篇文章會分享八個,給您參考看看! - 原文出處:https://dev.to/worldindev/8-javascript-tips-tricks-that-no-one-teaches-24g1 # 函數繼承 函數繼承就是先寫一個基礎函數,再寫一個擴充函數,來擴充屬性與方法,會像這樣: ``` // Base function function Drinks(data) { var that = {}; // Create an empty object that.name = data.name; // Add it a "name" property return that; // Return the object }; // Fuction which inherits from the base function function Coffee(data) { // Create the Drinks object var that = Drinks(data); // Extend base object that.giveName = function() { return 'This is ' + that.name; }; return that; }; // Usage var firstCoffee = Coffee({ name: 'Cappuccino' }); console.log(firstCoffee.giveName()); // Output: "This is Cappuccino" ``` # .map() 的替代方案 `.map()` 有一個替代方案,就是 `.from()`: ``` let dogs = [ { name: ‘Rio’, age: 2 }, { name: ‘Mac’, age: 3 }, { name: ‘Bruno’, age: 5 }, { name: ‘Jucas’, age: 10 }, { name: ‘Furr’, age: 8 }, { name: ‘Blu’, age: 7 }, ] let dogsNames = Array.from(dogs, ({name}) => name); console.log(dogsNames); // returns [“Rio”, “Mac”, “Bruno”, “Jucas”, “Furr”, “Blu”] ``` # 數字轉字串/字串轉數字 通常,要讓數字轉為字串,會這樣寫 ``` let num = 4 let newNum = num.toString(); ``` 然後字串轉數字,會這樣寫 ``` let num = "4" let stringNumber = Number(num); ``` 但有個更快的寫法是: ``` let num = 15; let numString = num + ""; // number to string let stringNum = + numString; // string to number ``` # 使用長度來調整和清空陣列 在 javascript 中,我們可以改寫 length 內建方法,直接設定一個新值。 舉個例: ``` let array_values = [1, 2, 3, 4, 5, 6, 7, 8]; console.log(array_values.length); // 8 array_values.length = 5; console.log(array_values.length); // 5 console.log(array_values); // [1, 2, 3, 4, 5] ``` 還能用來清空陣列: ``` let array_values = [1, 2, 3, 4, 5, 6, 7,8]; console.log(array_values.length); // 8 array_values.length = 0; console.log(array_values.length); // 0 console.log(array_values); // [] ``` # 使用陣列解構來交換值 陣列解構讓陣列或物件的屬性,可以拆出來到變數中。還可以用它來交換兩個元素,舉例如下: ``` let a = 1, b = 2 [a, b] = [b, a] console.log(a) // result -> 2 console.log(b) // result -> 1 ``` # 刪除陣列中的重複元素 這個技巧很簡單。比方說,我創建了一個包含重複元素的陣列,然後我想刪除重複項: ``` const array = [1, 3, 2, 3, 2, 1, true, false, true, 'Kio', 2, 3]; const filteredArray = [...new Set(array)]; console.log(filteredArray) // [1, 3, 2, true, false, "Kio"] ``` # for 迴圈的簡寫版 可以這樣寫: ``` const names = ["Kio", "Rio", "Mac"]; // Long Version for (let i = 0; i < names.length; i++) { const name = names[i]; console.log(name); } // Short Version for (let name of names) console.log(name); ``` # 效能監測 下面這張圖是 google 用秒數提示效能: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7ed89oyhcyyjhqirvc6.png 在 JavaScript 中也能做到,就像這樣: ``` const firstTime = performance.now(); something(); const secondTime = performance.now(); console.log(`The something function took ${secondTime - firstTime} milliseconds.`); ``` --- 以上八個技巧,希望對您有幫助!

JavaScript 中關於陣列的 13 個入門小技巧

陣列是 JavaScript 中最常用到的概念之一。處理資料時很常用到這種資料結構。這篇文章介紹一些陣列操作的基本方法,希望對你有幫助! - 原文出處:https://dev.to/duomly/13-useful-javascript-array-tips-and-tricks-you-should-know-2jfo # 1. 從陣列中移除重複項目 很經典的面試問題,需要從陣列中找出獨立值。使用 Set 資料型態,可以輕易完成這任務。這邊介紹兩種方法,一個使用 .from() 一個使用 ... 展開運算子。 ``` var fruits = [“banana”, “apple”, “orange”, “watermelon”, “apple”, “orange”, “grape”, “apple”]; // First method var uniqueFruits = Array.from(new Set(fruits)); console.log(uniqueFruits); // returns [“banana”, “apple”, “orange”, “watermelon”, “grape”] // Second method var uniqueFruits2 = […new Set(fruits)]; console.log(uniqueFruits2); // returns [“banana”, “apple”, “orange”, “watermelon”, “grape”] ``` 很簡單吧! # 2. 從陣列中抽換掉某個元素 這個情境也很常見。有一個簡單的好方法,就是使用 .splice ,參數分別是開始修改的位置、修改幾個元素、要更改成哪些值。 ``` var fruits = [“banana”, “apple”, “orange”, “watermelon”, “apple”, “orange”, “grape”, “apple”]; fruits.splice(0, 2, “potato”, “tomato”); console.log(fruits); // returns [“potato”, “tomato”, “orange”, “watermelon”, “apple”, “orange”, “grape”, “apple”] ``` # 3. 不用 .map() 的 map 功能 .map() 很常用、大家都會用,但有一個方法也很不錯,也很簡潔,就是 .from() ``` var friends = [ { name: ‘John’, age: 22 }, { name: ‘Peter’, age: 23 }, { name: ‘Mark’, age: 24 }, { name: ‘Maria’, age: 22 }, { name: ‘Monica’, age: 21 }, { name: ‘Martha’, age: 19 }, ] var friendsNames = Array.from(friends, ({name}) => name); console.log(friendsNames); // returns [“John”, “Peter”, “Mark”, “Maria”, “Monica”, “Martha”] ``` # 4. 清空陣列 有時候你會需要清空一個陣列。一個一個把元素刪掉太慢了,下面這個方法快多了:直接把長度設為 0 吧! ``` var fruits = [“banana”, “apple”, “orange”, “watermelon”, “apple”, “orange”, “grape”, “apple”]; fruits.length = 0; console.log(fruits); // returns [] ``` # 5. 把陣列轉換為物件 有時需要把陣列轉成物件。使用 ... 展開運算子可以很快完成這任務。 ``` var fruits = [“banana”, “apple”, “orange”, “watermelon”]; var fruitsObj = { …fruits }; console.log(fruitsObj); // returns {0: “banana”, 1: “apple”, 2: “orange”, 3: “watermelon”, 4: “apple”, 5: “orange”, 6: “grape”, 7: “apple”} ``` # 6. 陣列塞滿資料 有時需要把一個陣列塞滿某些資料。使用 .fill() 可以快速完成這任務: ``` var newArray = new Array(10).fill(“1”); console.log(newArray); // returns [“1”, “1”, “1”, “1”, “1”, “1”, “1”, “1”, “1”, “1”, “1”] ``` # 7. 合併陣列 老方法是使用 .concat() 來合併。但有個新方法,就是用 ... 展開運算子,非常簡單俐落: ``` var fruits = [“apple”, “banana”, “orange”]; var meat = [“poultry”, “beef”, “fish”]; var vegetables = [“potato”, “tomato”, “cucumber”]; var food = […fruits, …meat, …vegetables]; console.log(food); // [“apple”, “banana”, “orange”, “poultry”, “beef”, “fish”, “potato”, “tomato”, “cucumber”] ``` # 8. 找出陣列交集 這也是 JavaScript 面試最常見的問題。這邊使用 filter 跟 .includes 來完成這任務。 ``` var numOne = [0, 2, 4, 6, 8, 8]; var numTwo = [1, 2, 3, 4, 5, 6]; var duplicatedValues = […new Set(numOne)].filter(item => numTwo.includes(item)); console.log(duplicatedValues); // returns [2, 4, 6] ``` # 9. 把 false 相關的值從陣列移除 false 相關值包含 false, 0, "", null, NaN, undefined。使用 .filter() 可以俐落地過濾陣列。 ``` var mixedArr = [0, “blue”, “”, NaN, 9, true, undefined, “white”, false]; var trueArr = mixedArr.filter(Boolean); console.log(trueArr); // returns [“blue”, 9, true, “white”] ``` # 10. 從陣列撈出隨機元素 有時需要隨機撈元素出來。根據陣列長度,隨機撈一個索引出來,就可以囉。 ``` var colors = [“blue”, “white”, “green”, “navy”, “pink”, “purple”, “orange”, “yellow”, “black”, “brown”]; var randomColor = colors[(Math.floor(Math.random() * (colors.length)))] ``` # 11. 逆轉陣列 不需要複雜的迴圈跟函數,也能逆轉一個陣列。這樣一行就可以: ``` var colors = [“blue”, “white”, “green”, “navy”, “pink”, “purple”, “orange”, “yellow”, “black”, “brown”]; var reversedColors = colors.reverse(); console.log(reversedColors); // returns [“brown”, “black”, “yellow”, “orange”, “purple”, “pink”, “navy”, “green”, “white”, “blue”] ``` # 12. .lastIndexOf() 方法 這個有趣的方法,可以找到特定元素最後一次出現的索引。如果陣列中有重複值,就可用上這方法。 ``` var nums = [1, 5, 2, 6, 3, 5, 2, 3, 6, 5, 2, 7]; var lastIndex = nums.lastIndexOf(5); console.log(lastIndex); // returns 9 ``` # 13. 陣列中全部數值加總 這也是面試常見考題。使用 .reduce 可以快速解決這問題。 ``` var nums = [1, 5, 2, 6]; var sum = nums.reduce((x, y) => x + y); console.log(sum); // returns 14 ``` # 結論 這 13 個小技巧,分別有一些進階觀念可以研究,這篇文章作為入門介紹,希望對您的工作有幫助!

開發 React 時,推薦使用這些 Best Practices

在開發 React App 時,遵循一些 best practices 會使您的程式碼品質提高,這篇文章列出一些給您參考。 - 原文出處:https://dev.to/iambilalriaz/react-best-practices-ege # 強烈推薦 VS Code 作為 IDE Visual Studio Code 有幾個超好用的 React 功能。強大的外掛生態系,對開發者大有幫助: - Prettier - ES Lint - JavaScript (ES6) code snippets - Reactjs code snippets - Auto import # 使用 ES6 語法 程式碼越漂亮越好。在 JavaScript 中,採用 ES6 語法可以讓程式碼更簡潔。 ## Arrow Functions ``` // ES5 function getSum(a, b) { return a + b; } // ES6 const getSum = (a, b) => a + b; ``` ## Template Literal ``` // ES5 var name = "Bilal"; console.log("My name is " + name); // ES6 const name = "Bilal"; console.log(`My name is ${name}`); ``` ## const $ let const $ let 有各自的變數作用域。const 宣告的變數不能修改,let 宣告的變數可以修改。 ``` // ES5 var fruits = ["apple", "banana"]; // ES6 let fruits = ["apple", "banana"]; fruits.push("mango"); const workingHours = 8; ``` ## Object Destructuring ``` var person = { name: "John", age: 40, }; // ES5 var name = person.name; var age = person.age; // ES6 const { name, age } = person; ``` ## Defining Objects ``` var name = "John"; var age = 40; var designations = "Full Stack Developer"; var workingHours = 8; // ES5 var person = { name: name, age: age, designation: designation, workingHours: workingHours, }; // ES6 const person = { name, age, designation, workingHours }; ``` ES6 的語法特性、彈性,很多值得您一試。 # JSX 使用 map 時記得加上 key array map 時,永遠記得替每個元素加上獨立的 key 值。 ``` const students = [{id: 1, name: 'Bilal'}, {id: 2, name: 'Haris'}]; // in return function of component <ul> {students.map(({id, name}) => ( <li key={id}>{name}</li> ))} </ul>; ``` # 元件名稱使用 PascalCase ``` const helloText = () => <div>Hello</div>; // wrong const HelloText = () => <div>Hello</div>; // correct ``` # 變數和函數名稱使用 camelCase ``` const working_hours = 10; // bad approach const workingHours = 10; // good approach const get_sum = (a, b) => a + b; // bad approach const getSum = (a, b) => a + b; // good approach ``` # ID 和 css class 名稱使用 kebab-case ``` <!--bad approach--> <div className="hello_word" id="hello_world">Hello World</div> <!--good approach --> <div className="hello-word" id="hello-world">Hello World</div> ``` # 永遠要檢查物件&陣列的 null & undefined 忘記檢查的話,常常會導致一堆錯誤。 所以要保持檢查的習慣。 ``` const person = { name: "Haris", city: "Lahore", }; console.log("Age", person.age); // error console.log("Age", person.age ? person.age : 20); // correct console.log("Age", person.age ?? 20); //correct const oddNumbers = undefined; console.log(oddNumbers.length); // error console.log(oddNumbers.length ? oddNumbers.length : "Array is undefined"); // correct console.log(oddNumbers.length ?? "Array is undefined"); // correct ``` # 避免 Inline Styling Inline styling 會讓 jsx 程式碼變得很亂。用單獨的 css 文件拆分出來比較好。 ``` const text = <div style={{ fontWeight: "bold" }}>Happy Learing!</div>; // bad approach const text = <div className="learning-text">Happy Learing!</div>; // good approach ``` 在 .css 文件中: ``` .learning-text { font-weight: bold; } ``` # 避免 DOM 操作 用 React state 為主,別用 DOM 操作 糟糕做法: ``` <div id="error-msg">Please enter a valid value</div> ``` ``` document.getElementById("error-msg").visibility = visible; ``` 推薦做法: ``` const [isValid, setIsValid] = useState(false); <div hidden={isValid}>Please enter a valid value</div>; ``` 使用 isValid 來管理 UI 顯示邏輯。 # 在 useEffect 記得清乾淨每個事件監聽器 加過的事件監聽器,都要記得清乾淨: ``` const printHello = () => console.log("HELLO"); useEffect(() => { document.addEventListener("click", printHello); return () => document.removeEventListener("click", printHello); }); ``` # 避免重複開發,多寫通用元件 讓程式碼越乾淨越好。相似的東西可以寫通用元件。再根據 props 內容傳遞來處理相異處即可: ``` const Input=(props)=>{ const [inputValue, setInputValue]=useState(''); return( <label>{props.thing}</label> <input type='text' value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> ) } ``` 在其他元件中,就能這樣用: ``` <div> <Input thing="First Name" /> <Input thing="Second Name" /> </div> ``` # 檔案要分類一下 相關檔案可以分類成一個資料夾。 例如在 React 寫一個導覽列,那就可以開一個資料夾,相關的 .js 與 .css 檔案放裡面。 # 寫 functional components 為主 簡單顯示一些東西、沒用到 state 的話,那寫 functional components 比寫 class components 好。 如果你會寫 react hooks 的話,那就連 state 都完全不成問題。 # 養成編寫輔助函數的習慣 有時你在 React App 中會需要一些通用功能。 這種情況,可以開一個 `helper-functions.js` 檔案,在裡面寫輔助函數,就可以到處使用了。 ## 使用三元運算子代替 if/else if 使用 `if/else if` 語句會使程式碼變得龐大。使用三元運算子,就簡潔、清楚多了: ``` // Bad approach if (name === "Ali") { return 1; } else if (name === "Bilal") { return 2; } else { return 3; } // Good approach name === "Ali" ? 1 : name === "Bilal" ? 2 : 3; ``` # 新增 index.js 檔案,讓匯入元件更簡單 如果你在 actions 資料夾中有一個 index.js 檔案,當你想在元件中導入時,會像這樣: ``` import { actionName } from "src/redux/actions"; ``` actions 後面的 index.js 可以省略不寫,就不用這樣囉唆了: ``` import { actionName } from "src/redux/actions/index"; ``` # Destructuring of Props 物件屬性名稱可以拆出來,後面用起來比較方便。 假設你的元件有 `name`、`age` 和 `designation` 這些 props: ``` // Bad approach const Details = (props) => { return ( <div> <p>{props.name}</p> <p>{props.age}</p> <p>{props.designation}</p> </div> ); }; // Good approach const Details = ({ name, age, designation }) => { return ( <div> <p>{name}</p> <p>{age}</p> <p>{designation}</p> </div> ); }; ``` # 不要嘗試在同一函數中,去碰修改過的 state 變數 在一個函數中,如果你正在為一個狀態變數賦值,在同一個函數中,是拿不到新值的 ``` const Message = () => { const [message, setMessage] = useState("Hello World"); const changeMessage = (messageText) => { setMessage("Happy Learning"); console.log(message); // It will print Hello World on console }; return <div>{message}</div>; }; ``` # 使用 === 運算子代替 == 在比較兩個值時,嚴格檢查變數型別比較好: ``` "2" == 2 ? true : false; // true "2" === 2 ? true : false; // false ``` --- 以上 Best Practices 供您參考,祝福您不斷變強!

10 個現代 JavaScript 中的好用語法:新手推薦

在 ES6 版本之後,Javascript 多了很多好用的語法,建議新手一定要學起來這幾招。 - 原文出處:https://dev.to/azure/modern-javascript-10-things-you-should-be-using-starting-today-1adm # -1- 展開運算子 Spred operator 在物件或陣列前面寫 ... 可以很輕鬆地把資料展開,舉例來說: ## Spread array ``` let firstHalf = [ 'one', 'two']; let secondHalf = ['three', 'four', ...firstHalf]; ``` 寫起來非常簡潔。不然原本要這樣寫: ## NO Array spread ``` let firstHalf = [ 'one', 'two']; let secondHalf = ['three', 'four']; for(var i=0, i <firstHalf.length; i++ ) { secondHalf.push(firstHalf[i]); } ``` 用在物件也可以,能夠合併屬性: ## Spread object ``` const hero = { name: 'Xena - Warrior Princess', realName: 'Lucy Lawless' } const heroWithSword = { ...hero, weapon: 'sword' } ``` 不然的話,就要用迴圈來跑全部屬性: ## NO Object spread ``` let keys = Object.keys(hero); let obj = {}; for(var i=0; i< keys.length; i++) { obj[keys[i]] = keys[props[i]]; } ``` 舊寫法其實有 Object.assign() 可以用,看起來會像: ``` const heroWithSword = Object.assign({}, hero, {weapon:"sword"}) ``` 但展開運算子還是簡潔多了: ``` const heroWithSword = { ...hero, weapon: 'sword' } ``` # -2- 其餘參數 Rest parameter 其餘參數能夠將剩下的參數蒐集到一個陣列中。JavaScript 有足夠彈性可以處理這些參數。通常會這樣寫: ``` function add(first, second, ...remaining) { return first + second; } ``` 以上只是加總前兩個參數,也就是 add(1,2) 或 add(1,2,3, 4) 結果一樣。接著這樣改寫: ``` function add(first, second, ...remaining) { return first + second + remaining.reduce((acc, curr) => acc + curr, 0); } ``` 這樣就會把全部參數都加總在一起了。 如您所見,前面加上 ... 就可以搜集其餘參數。這是一種命名變數的方式而已,比較少人知道這招,但可以知道一下。 # -3- 字串插值 String interpolation 看過這種寫法嗎? ``` class Product { constructor(name, description, price) { this.name = name; this.description = description; this.price = price; } getDescription() { return " Full description \n" + " name: " + this.name + " description: " + this.description } } ``` 看看那個 getDescription() 方法,又長、跨行、又難讀。在大多數程式語言都只能這樣寫。幸好,有些程式語言支援字串插值,包括 JavaScript,所以可以改寫如下: ``` getDescription() { return `Full description \n: name: ${this.name} description ${this.description} `; } ``` 反引號 ` 可以定義多行字串。再使用 ${} 進行插值。是不是好多了呢:) # -4- 屬性縮寫 Shorthand properties 雖然還沒搞懂,你可能早就在用了。在 ES5 需要這樣寫: ``` function createCoord(x, y) { return { x: x, y: y } } ``` 在 ES6 之後,如果 : 後面名稱一樣,可以直接省略,像這樣: ``` function createCoord(x, y) { return { x, y } } ``` 簡潔多了吧? # -5- 方法屬性 Method properties 在物件裡面指向方法的寫法。以下是 ES5 寫法: ``` const math = { add: function(a,b) { return a + b; }, sub: function(a,b) { return a - b; }, multiply: function(a,b) { return a * b; } } ``` 在 ES6 之後可以少寫一大堆,像這樣: ``` const math = { add(a,b) { return a + b; }, sub(a,b) { return a - b; }, multiply(a,b) { return a * b; } } ``` # -6- 解構 Destructuring 解構用得好,有益身心健康。 ## Object destructuring 這段: ``` function handle(req, res) { const name = req.body.name; const description = req.body.description; const url = req.url; log('url endpoint', url); // lots of logic happening dbService.createPerson( name, description ) } ``` 有點難讀吧,但要從多層深度挖資料,就會寫成這樣。還能怎麼辦?其實有以下妙招喔: ``` function handle(req, res) { const { body: { name, description }, url }, = req; log('url endpoint', url); // lots of logic happening dbService.createPerson( name, description ) ``` 變成俐落的一行了。 ## Array destructuring 不只物件能用,陣列也可以用,像這段: ``` const array = [1,2,3,4,5,6]; const a = array[0]; const c = array[2]; ``` 可以更優雅地改寫成這樣: ``` const array = [1,2,3,4,5,6]; const [a, ,c, ...remaining] = array; // remaining = [4,5,6] ``` 上面的模式配對,就可以拆出我們需要的變數。如果要跳過某值,就寫 , , 即可。我另外多寫了一個 rest parameter 來取得其餘資料。 ## Parameter matching 函數的參數也能這樣寫。當函數有超過 2-3 個參數時,業界慣例常常是這樣寫: ``` function doSomething(config) { if(config.a) { ... } if(config.b) { ... } if(config.c) { ... } } ``` 更漂亮的寫法其實是: ``` function doSomething({ a, b, c }) { if(a) { ... } if(b) { ... } if(c) { ... } } ``` # -7- 陣列方法 ES6 有大量好用的陣列方法,例如: - find(),回傳符合條件的項目,否則回傳 null - findIndex(),尋找項目的索引 - some(), 確認是否包含符合條件的項目 - includes(), 確認是否包含某個項目 舉例說明如下: ``` const array = [{ id: 1, checked: true }, { id: 2 }]; arr.find(item => item.id === 2) // { id: 2 } arr.findIndex(item => item.id === 2) // 1 arr.some(item => item.checked) // true const numberArray = [1,2,3,4]; numberArray.includes(2) // true ``` # -8- Promises + Async/Await 在只有 callback 能寫的年代,這種寫法很常見: ``` function doSomething(cb) { setTimeout(() => { cb('done') }, 3000) } doSomething((arg) => { console.log('done here', arg); }) ``` 用來寫非同步任務時,就會這樣寫。但現在我們有 promise 能寫了,所以可以這樣: ``` function doSomething() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('done') }, 3000) }) } doSomething().then(arg => { console.log('done here', arg); }) ``` 多個流程還能整個串起來,就像這樣: ``` getUser() .then(getOrderByUser) .then(getOrderItemsByOrder) .then(orderItems => { // do something with order items }) ``` ## Async/await 接著還有 async/await 可以用。上方的範例,相關 promise 變成: ``` async function getItems() { try { const user = await getUser(); const order = await getOrderByUser(user); const items = await getOrderItemsByOrder(order); return items; } catch(err) { // handle error here, the suggestion to return something or rethrow } } getItems().then(items => { // do something with order items }) ``` 看起來像是同步,但其實非同步的一段程式碼:) # -9- 模塊 Modules 幾乎所有程式語言都支援模塊功能。將程式碼分為多個檔案,各自為獨立單元,就是模塊了。像這樣: ``` // math.js export function add(a,b) { return a + b; } export function sub(a,b) { return a - b; } export default (a,b) => a * b; // main.js import mult, { add, sub } from './math'; mult(2, 4) // 8 add(1,1) // 2 sub(1,2) // -1 ``` 使用 export 關鍵字來表示 add 跟 sub 是公開可用的。export default 代表不指定函數時直接導入的內容。在 main.js 中,將預設導入命名為 mult,然後指定導入函數 add 和 sub。 # -10- 箭頭函數 + this 整篇文章一直在用箭頭函數,其實只是另一種函數的寫法而已。以前要這樣寫: ``` function printArray(arr) { // do something } ``` 現在可以這樣寫: ``` const printArray = (arr) => { // do something } ``` ## 單行函數 函數還可以用一行寫完: ``` const add = (a,b) => a + b ``` 這種寫法可以省略 return 直接回傳結果。要回傳物件也可以,這樣即可: ``` const create = (a,b) = > ({ x: a, y: b }) ``` ## Lexical this 以前常常會搞混 this 代表什麼,舉例來說: ``` let array = [1,2,3]; function sum() { this.total = 0; arr.forEach(function(item) { this.total+= item; // `this` is the inner functions `this`, BAD }) return total; } ``` 這個例子中,this 在 forEach 指錯地方了。以前的解法是: ``` function sum() { this.total = 0; var self = this; arr.forEach(function(item) { self.total+= item; // now we are using `self`, it solves it but feels hacky }) return total; } ``` 雖然多寫個 self 變數可以解決,但實在很鳥。新的箭頭函數寫法則解決了這問題: ``` function sum() { this.total = 0; arr.forEach((item) => { this.total+= item; // all is well `this` points to outer function }) return total; } ``` # 結論 ES6 之後有很多好東西可以用,今天先介紹一些,希望對大家有幫助!