0%

距離上一篇是三年前,也表示三年沒來這裡了。時間總是過的快,這三年我做了什麼?有許多時間拿來和家人相處,更有許多時間在思考與摸索。
自從真的理解與接受「這世界唯一不變得就是變」我的世界明亮了起來,我理解「放下」的重要,說穿了就是接受,但接受之後時間的繼續,促使妳應該要爬起來,拍拍灰塵繼續向前走。
這世界有許多東西是無用的,無用的擔心、無用的恐懼、無用的學習?而無用的學習是否真的無用?應該不至於,那小小的記憶曾在你的腦肌裡,就待需要的時候會自動跳出來幫你。每次感嘆自己化太多時間在紀錄時,總是這樣安慰自己:總有一天會用到。而現在,我也因為當年寫的 Hexo 筆記而幫到自己。

AI 時代的來臨不需呻吟,學習就是了

是的,學也學不完,但還是想學。學習幫助我們思考、幫助我們優化,沒什麼不好。就這樣,我會繼續學,並是著保持平衡:家人與生活要排第一,雖然有時很難,但先讓自己站穩,才能有機會幫其他人,而學習是手段,幫人是目的,這樣想就好了。

今年給自己的目標是把某一個領域學會學熟,當年的 JS 是我嚮往的,但是它是如此高又遠,框架與技術一個一個來,但真的都要學嗎?如果以目標取向來說,只要任何一種能達成目的的工具都是好工具,哪麼衡量學習曲線所付出的時間就得自己評估,而我們也會高估自己(我啦!),現在比較會量力而為,同時接受新挑戰,既然世界會變,那就儘量掌握底層邏輯,至少邏輯變動不至於太大。掌握我們可以掌握的就好,但仍要「盡人士聽天命」就是。

學習是條孤獨的路,尤其自己又是喜歡分享的人,所以能碰到同好是非常開心的,希望今年也能像六年前,遇到一群一起學習的夥伴,一起向前。

時代推著我們走,技術也是。WP推出 6.2 版了,對 WP 的認知也應該要升級才是。透過官方文件,就再一次重新認識這個老朋友吧!

什麼是區塊?

重點:

  • 區塊編輯器,也稱為 Gutenberg,是 TinyMCE 的現代替代品,它是 WordPress 傳統配備的編輯器,您可能已經熟悉。
  • 儘管它仍在不斷發展中,但區塊編輯器已經足夠成熟,可以成為新 WordPress 安裝的默認編輯器。
  • 區塊名稱的作用可透過區塊名稱理解,區塊編輯器可以讓我們將內容編輯為稱為「區塊」的離散單元,其中每個「區塊」包含某種類型的內容,例如段落、標題或圖像。如果之前使用過 Gutenberg,則可能知道其他類型的區塊,例如封面區塊、引用區塊或畫廊區塊。每個區塊都是文章或頁面中的一個元素。
  • Gutenberg 區塊是用 React 構建的。我們可以從 WordPress 插件中實做自定義區塊,我們可以建立一個外掛,外掛主要是使用 JavaScript 和 React 來實作,而不是 PHP。
  • 某些類型的區塊(例如段落區塊或圖像區塊)是包含單個內容項目的「基本元素」又稱「基元」。其他類型的區塊可以包含許多「基元」區塊。這種類型的區塊的一個例子是群組區塊,它允許我們將區塊分組在一起,以便將它們視為單個單位。
  • 文章(article)或頁面(page)的內容仍然像以前基於 TinyMCE 的編輯器一樣存儲在數據庫的 wp_posts 表中。但是,區塊編輯器在內容中插入 HTML 註釋,以劃分個別區塊並提供此區塊的訊息,而在瀏覽器中呈現區塊的樣貌。

透過這個連結可以直接操作 Gutenberg編輯器喔。

小時候不愛唸書,長大還是要還這些小時候欠的讀書債。因為在學校學了很多年,也放棄了好多年,直到出國才知道英文像把鑰匙,沒有這把鑰匙就無法好好的理解世界,尤其是理解自己喜歡的事物。於是乎出社會後試著想把這些國中遺棄的英文找回來,卻發現不是那麼容易,隨著年齡的增長,記憶與理解力並沒有隨之生長,反而是有下降的趨勢,越學越記越吃力,但真的是如此嗎?腦力是無窮的,應該還是可以靠一些方法逆轉吧?於是把自己當成一個實驗體,就試著不同的方法學習,看是不是可以把一些基礎打好並活化腦細胞。

背單字一直是個學語言的痛點,一個單字記起來了,並不表示就知道怎麼用在話語裡,介於「知道」這個字和「使用」這個字之間,兩者之間有著一段很長的距離。既然無法一下子就會用這個字,那麼至少「記住」它是基礎的,所以試著各種方法背單字。

今天聽到一個名為格林法則的背單自方式,查了一下 wiki 寫著:
「格林法則(Grimm’s law)」又稱「第一次子音推移」,是一種用來描述印歐語語音遞變的定律,由德國語言學家雅各布‧格林(Jakob Grimm)提出。 利用英文的形、聲、義,找出簡單字彙如何演變成艱深單字的方法,推翻以往用字母順序記憶單字的古板方式。

格林法則ㄧ 將母音互換

法則一影片
被單字時,試著將單字中的母音替換成其他母音:a / e / i / o / u / y 互換
例如單字: drink 換成 drank -> drunk… 事實上,許多不規則的動詞變化都是這樣變過來的。

  • sing, sang, sung
  • sink,sank, sunk
  • run, ran, run
  • ring, rang, rung
  • swim, swam, swum
  • begin, began, begun

另一個例子是 hot, a.熱的,但是以格林法則的方式,把中間的母音 o 換成 ea 變成 heat 則成為 n.熱 / vt.加熱。

再舉一個例子 sit, vi.坐,把中間的 i 換成 ea 成為 seat 就成為 n.位子 / vt. 使就坐。

格林法則允許將一個母音互換成兩個字母的母音,因為發音群是相同的。

  • best a.最好的 –> boast vi.自誇(+about/of)
  • fire n.火 –> fury n.怒火 –> furi.ous(adj字尾,表示多) a.盛怒的 –>
  • -> in.furi.ate (in 為 on 持續/.ate n./v. 字尾) vt.使大怒

格林法則二 子音 d / t / s 可互換

法則二影片

待續…

因為做練習接觸稻穀羅馬數字,才知道原來以前的數字那麼複雜、限制那麼多,而現在我們每天在用的阿拉伯數字卻是如此方便!人類的進化總是朝著更方便的方向走,用到後來覺得一切都如此合理與正常,卻不知在遠久的年代裡,也曾有這樣的紀錄數字方式,以及進位的演化。

每學一樣知識總是有驚嘆的部份,很難想像那沒有「零」的年代,且是一種談論禁忌,而在唸出數字時需要花腦筋想一下才能解讀數字,真的很神奇。

這個習題是寫出一個函式,給一個阿拉伯數字,必須回傳羅馬數字,當然也應該要了解一下羅馬數字的規則,感謝網路找到這個解說,算是最清楚了,這篇是觀看這個影片的筆記。知道規則後就開始思考如何轉化成 JS,前人寫的這篇也很值得參考。

認識古羅馬數字的字符

基本字符:
I = 1
V = 5
X = 10
L = 50
C = 100
D = 500
M = 1000
重點:七個拉丁字母 / 數字5 很重要 / 是種十進制系統 / 字符的值與位置無關(非位置計數法)

計數規則

- 相同的字符相連,表示的數=字符相加得到的數 → III = 3
- **右加左減** 小的字符在大的字符的右邊,表示的數等於這些字符相加的數 → VIII = 8 / XII = 12
- 只有 I, X, C 可以把小的字符寫在大的字符的左邊,所表示的數等於大數減小數所得到的數  → IV=4 (5減左邊的1)  / IX = 9 (10減左邊的1)。這種情況下,大的字符左邊最多只能有一個小的字符。8 不可以寫成 IIX,而是 VIII
- I 右邊最大字符為 X、 X 右邊最大字符為 C、 C 右邊最大字符為 M。99 = XCIM 而不是 IC
- 相同字符不可重複超過三次 4 = IV  (但仍有古蹟是寫IIII)
- 在一個數的上面畫橫線,表示這個數增值1000倍。 V = 5, V上面有橫線為5000
- 滿足上面規則的情況下,要用最少的字符表示,如100 = C,而不是LL。

計數範例

- I, II, III, IV, V, VI, VII, VIII, IX, X, XI,XII (1-12)
- 91  = XCI   → C100-X10 + I 1
- 2017 = MMXVII → MM 2000 + X 10 + VII 7
- 12345 = XIICCCXLV  → XII 12000 + CCC 300 + XLV 45 (L50 - X 10 + V 5)
- XLIIDC = 42600  → XLII (L50- X10 + II 2) + DC (D500 + C100)
- 加法運算  XIX + XXII = XLI  19+22 = 41
- LXXVII - XLVIII = XXIX  77 - 48 =

關於 0

- 羅馬計數系統中沒有表示0的字符。歐洲教會阻止0的傳播。
- 公元725年,聖彼得主教用字母N(N是 nulla 的簡稱,拉丁文解釋為零)代表零。

其他

- 小寫拉丁字母表示數字 i, ii, iii, iv… 西羅馬帝國滅亡之後,開始引入小寫,規則與大寫相同,但使用範圍與用途不同,大寫是在建築、鐘錶、日曆、書籍文件、人名X世、文章編號。
- 小寫用在有序列表(ordered list)的編號上

###
先決定從大到小還是從小到大去對應
遇到5 轉成 V(1位) ,遇到10 轉成 X(2位)
從左到右逐字判斷

useEffect() 本身是個函式,由 React 框架提供。

什麼時候使用 useEffect()

當元件狀態(useState)或變數變動之後,要做什麼事情可透過useEffect來監聽執行。
useEffect() 也是個通用的 Hook,找不到對應的 Hook 使用時也可以使用 useEffect()

例如當我們載某個元件載入之後,希望網頁標題隨之變化顯示:「歡迎來這裡」,那麼改變網頁標題這個動作就需要透過 useEffect 來完成。

1
2
3
4
5
6
7
8
9
10
11
import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const Welcome=(props)=>{
useEffect(() => {
document.title = '歡迎來這裡'
})
return <h1>Hello, {props.name}</h1>
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Welcome name="Tracy" />, rootElement);

useEffect() 做什麼事?

以上述的例子來說,useEffect() 的參數是函式,這個函式要完成的就是改變網頁的標題,元件載入後 React 就會去執行這個參數的函式。元件每渲染一次,此函式就自動執行一次,元件在第一次載入頁面時,這個函式也會執行。

useEffect() 的第二個參數

剛剛說過 useEffect()的第一個參數是函式,第二個參數何時用呢?
有時候我們並不希望 useEffect() 每次渲染都執行,這時第二參數就派上用場,使用一個 Array 來指定 useEffect() 的依賴項(資料),只有依賴項發生變化,才會重新渲染。

1
2
3
4
5
6
const Welcome=(props)=>{
useEffect(() => {
document.title = `hello, ${props.name}`
},[props.name])
return <h1>Hello, {props.name}</h1>
}

上述例子中,useEffect()的第二個參數是 Array,指定第一個參數函式的依賴項(props.name)只有此依賴項的變數產生變化時,第一參數的函式才會執行。
如果第二參數是一個空的 Array,就表示第一個參數函式沒有依賴項,也因此第個參數函式只會在元件載入頁面時執行一次,之後元件重新渲染時就不會執行了。

useEffect() 的用途

useEffect() 常見的用途有:

  • 獲取資料/call API (data fetching)
  • 事件的監聽或訂閱(setting up a subscription)
  • 改變 DOM 元素(changing the DOM)
  • 輸出日誌(logging)

使用 useEffect call API

終於寫到重點了,useEffect()最常用來獲取遠端伺服器上的資料(call API),以下是範例:
此範例使用 axios 套件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, {useState, useEffect} from 'react'
import axios from 'axios'

const urlAPI = 'https://hn.algolia.com/api/v1/search'

const GetHitList = () => {
const [data, setData] = useState({hits:[]}) // hit: 轟動一時的人的列表

useEffect(()=>{
const fetchData = async () => {
const result = await axios(`${urlAPI}?query=redux`)

setDate(result.data)
}

fetchData()
},[])
return (
<ul>
{data.hits.map((item) => {
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
})}
</ul>
)
}

export default GetHitList
  • useState()來產生一個狀態變數data來保存取的得的資料;
  • useEffect()的第一參數函式使用 async 用來取得從遠端伺服器上非同步取得的資料;
  • 取得資料後再用setData()來觸發元件的重新渲染;
  • 因為取得資料只需要一次,所以useState()的第二參數依賴項為空的 Array;

其他 Call API 的寫法

以下範例是以useEffect()呼叫 API ,並把回傳的圖片 url 顯示到畫面上的三種寫法,都是使用fetch()

  • fetch()是XMLHttpRequest 的升級版,用於在 JavaScript 裡發出 HTTP 請求時使用。
  • fetch()使用Promise,不使用 callback 函式,因此大大簡化了寫法,寫起來更簡潔。

第一種寫法 fetch, async / await

1
2
3
4
5
6
7
8
9
10
11
12
13
function CallApi() {
const [data, setData] = React.useState({})
const urlAPI = 'https://dog.ceo/api/breeds/image/random'
// 第一種寫法 async / await 1.
React.useEffect(()=>{
// 先把 call API 的邏輯放到一個單獨的函式中,再呼叫它
const fetchAPI = async () => {
const res = await fetch(urlAPI)
const resJson = await res.json()
setData(resJson)
}
fetchAPI()
}, [])

第二種寫法 fetch, async / await + try / catch

1
2
3
4
5
6
7
8
9
10
11
12
React.useEffect(()=>{
const getData = async () => {
try {
const res = await fetch(urlAPI)
const resJson = await res.json()
return setData(resJson)
} catch (err) {
console.log(err)
}
}
getData()
}, [])

第二種寫法 fetch, then / catch

1
2
3
4
5
6
7
8
9
10
11
12
React.useEffect(()=>{
fetch(urlAPI)
.then(res => res.json())
.then(resJson => setData(resJson))
.catch(err => console.log(err))
}, [])

return <div>
{
data.message && <img src={data.message} />
}
</div>

useEffect() 何時需要回傳值?

useEffect()是隨著元件載入的時候執行的,那麼在元件卸載時,有時也需要清理這些useEffect()useEffect()可以回傳一個函式,在元件卸載時執行這個函式就可以清除。
如果不需要清除useEffect()第一參數函式所做的事,就可以不用回傳任何值。
例如,在元件載入時,我們使用useEffect()在加載時訂閱了一個事件,並希望在元件卸載時取消訂閱,這時就可以用useEffect()回傳的函式去取消。

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe()
return () => {
subscription.unsubscribe()
}
},[props,source])

使用 useEffect() 的注意事項

  • 一次只做一件事,不要在useEffect()裡一次做多件事,例如 TodoList 有讀取、寫入、刪除,這三件事需要寫成三個useEffect()

最近發現以 Vue Vite 的方式來設置環境超級快,build 也快,開啟也快,但是因為有些舊專案仍然需要用的 Vue-CLI 所以就退回來以 Vue-CLI 設置專案,順便也想把 TailwindCss 設置好,才發現有許多眉眉角角的事情要注意。

通常我們會先去看官方文件怎麼寫,但是解決方案真的只有官方文件寫的那樣嗎?還有,網路上的資源那麼多,但是真的對你而言都適用嗎?在出現錯誤的過程中,我們匍匐前進,但是如果不去推敲,很難找出為什麼跑不起來的原因。例如,只是想要簡單的使用 TailwindCss,卻不想使用 CDN 的方式引進來,於是去 TailwindCss 的官網看,發現還有 Tailwind CLI、Using PostCSS、Framework Guides 這幾種。先從 Tailwind CLI 的方式來,卻怎麼樣也裝不起來,或許是配置的設定不熟,於是退而求其次的試著 Using PostCSS 的方式安裝,跑步起來,加上自己要的只是簡單的介面,不置於客製化或加上自己的樣式,所以繼續看下去…。這時候以 Vue-CLI 安裝的配置被自己弄得好複雜,依照網路上找到的設置也跑步起來,打掉重練,最後找到這個 vue-cli-plugin-tailwind,只要再安裝完 Vue-CLI,加上這麼一個神奇的指令vue add tailwind就,好,了。
當然,安裝套件也是有依賴的風險,但是這算是目前用最順的方式,所以在此記錄一下。

環境設置需求:

  • MacOS
  • Vue3
  • Vue-CLI
  • TailwindCss3

方法

  • 以 Vue-CLI 安裝好 Vue3 之後,再補上vue add tailwind指令來安裝 vue-cli-plugin-tailwind 這個套件。
  • 套件會自動生成 tailwind.config.js 設置檔案。
  • 在根目錄底下的 main.js 也會自動引入 import './assets/tailwind.css'
  • 這時 src 底下的 assets 會自動生成 tailwind.css 裡面會引入 tailwind 主要的三支 Css : base, components 和 utilities。
  • 同時也會自動生成一個 postcss.config.js,主要是可以改變 build 出來後的 CSS 檔案。

接下來就可以寫入 TailwindCss 的語法在頁面上試試了。

後續在網路上查找發先已經有前輩介紹的更詳細,如果想瞭解更細部的設定可參考:Vue CLI 安裝 Tailwind CSS,雖是2020年的文章,但大致上安裝方式沒什麼變喔~

MDN的 Document Object Model (DOM) 介紹了許多 DOM 的相關資訊,有機會用到的話,再來仔細研究一下介紹之外的其他方法,這裡只會介紹最常用的部份。
之前曾寫過一篇DOM - 操控網頁元素的必學技巧這篇就算是再一次複習吧!

DOM 到底是什麼?

在學習如何操控 DOM 之前,先來好好的理解什麼是 DOM :

DOM 是文件物件模型(Document, Object, Model)與就是用來呈現與操控 HTML 的程式介面,或 XML 檔案的互動 API。也就是把 HTML 標記(Document)轉換成物件(Object)。
這個文件物件模型提供了一個文件(樹狀)的結構表示法,並且定義讓程式可以存取或修改文件的架構、風格與內容的方法。

DOM 提供了如樹狀結構的節點,這些樹狀結構的節點都可以有自己的屬性,在節點上也可以綁定事件處理的程序,一旦這些節點因使用者行為被觸發,就會執行綁定的程序。
簡單的說 DOM 就是 JS 跟 HTML 溝通的橋樑(牽紅線)。DOM 允許我們透過 JS 來操作 HTML(Document) 這個物件,但不能直接操控頁面上的標籤,DOM 所做的事就是瀏覽器幫 Object 轉換成 HTML 對應的標記,進而讓 JS 可以改變頁面上的元素。

什麼是 Window Object

在 JS 當中的 window Object,是指我們目前程式正在運行的電腦螢幕視窗(瀏覽器視窗)。這個 window 物件裡有許多方法,例如window.alert()或是window.addEventListener()透過這些 Window 物件裡的方法,來操控物件裡的元素。以下列舉幾個常用的方法,因 JS 本身會是寫在 Window Object 這個物件裡,所以寫 JS 時 window 可省略:

使用 JavaScript 來操控網頁元素(DOM)有三種方法:

  • 介面:如何改變介面
  • 事件:如何監聽事件並做出反應
  • 資料:如何與伺服器交換資料

選取 DOM 元素

  • Tag
  • Class Name
  • ID (getElementById) Element 為單數,因為 ID 只有一個。
  • CSS 選擇器
  • element.closest() 最靠近父層的元素
1
2
3
4
5
const header = document.getElementsByTagName('header'); // tag 指 HTML 標籤
const box = document.getElementsByClassName('box'); // class name
const icebox = document.getElementById('ice-box'); // ID
const icebox_p = document.querySelector('#ice-box p'); // 匹配到的第一個 #ice-box p
const icebox_p_all = document.querySelectorAll('#ice-box p'); // // 匹配到的 所有 #ice-box p ,抓取成為 NodeList Array

CSS 選擇器

  • CSS 選擇器最常使用querySelectorquerySelectorAll
  • querySelector只會抓取並回傳第一個找到的元素,
  • querySelectorAll 會選取匹配條件的元素,並且把收集到的元素存成陣列(NodeList)回傳回來。

改變 CSS

我們可以直接改變 DOM 的樣式,但是累贅,需要一個一個寫入 style 的屬性,最常處理的方式是在元素上加上 class 類別,需要時例如事件觸發時,再加上狀態或屬性。

1
2
const linkA = document.querySelector('.box a');
linkA.classList.add('active');

增加或刪減 className

  • 增加:.classList.add('active');
  • 移除:.classList.remove('active');
  • 切換:.classList.toggle('active'); 如果沒這個 class 就新增,有就移除
  • 包含:是否包含此 class.classList.contains('active'); 回傳布林值

取得、改變文字或標籤內容

會替代掉原本的元素內文字或內容

  • [DOM].innerText =
    只抓取元素的內容,不包含子元素,只能寫入存文字
  • element.innerHTML =
    會抓取元素的內容,包含子元素,且寫入標籤元素
    ex.element.innerHTML = '<div>Hello<div>'
  • element.outerHTML=
    不只改變元件內容,也可替換掉元件本身的標籤
    ex.element.outerHTML = '<div>Hello<div>'

加入與移除元素

  • Element.append()
    是在元素的最後一個子節點之後插入一組 Node 物件或是 DOMString 物件,被插入的 DOMString 物件等價為 Text 節點。

  • Element.appendChild(aChild)
    將一個節點加到指定的父節點,子節點的列表的末端。如果某個結點已經擁有父節點,使用此方法後,此節點會先被移除,在被插入到新的位置。如果要保留原本的節點,可先使用Node.cloneNode()來複製一份,再將這個複製的節點附加到目標父節點下。而使用Node.cloneNode()複製的節點不會自動保持同步。

  • Node.removeChild()
    從 DOM 移除一個子節點,並回傳移除的節點。

1
2
3
// 創建一個新的段落元素 <p>,然後增加到 <body> 的最尾端
const p = document.createElement('p');
document.body.appendChild(p);

複製元素

  • Node.cloneNode(Deep)
    參數如果是 tree,表示為深度 clone, 節點的所有的後代節點都會被複製,如果是 false 則只會複製節點本身。
    如是 深度 clone,需注意後代節點中是否有唯一的 ID
1
2
const p = document.getElementById('para1');
const p_prime = p.cloneNode(true);

學習程式就是如此,常常沒用的東西就忘了,要用的時會再來找其實也可以,但是如果可以使用的更熟練,應該可以省下一些時間找資料吧。

展開與其餘運算子是 ES6 的特性之一,這兩個運算子的寫法都是三個點 … ,這樣的寫法使得在做一些 Array 或 JSON 的處理時,寫法簡潔很多。
透過這篇筆記的整理,希望能更熟悉的應用在自己的實作之中。

  • 展開與其餘運算子的符號是一模一樣的(…)三個點,但情況與意義不同。
  • 中文(…)通常代表其餘更多或忽略,英文 Ellipsis 代表省略,也會用”three dots”與”dot-dot-dot”這三個點
  • 應用在 Array 上,是 ES6 中的其中兩種新特性
  • Spread 展開 : 展開陣列中的值
  • Rest 其餘 : 集合其餘的值成為陣列

展開運算子(spread operator)

摘要

  • 展開運算子可輕易合併多個陣列
  • 可拿來淺拷貝,必免更動到原資料
  • 可以 slice() 來指定取出的陣列元素
  • 也可使用在物件裡並加入資料
  • 可將類陣列轉成一般陣列
  • 可把某個陣列展開,然後傳入函式作為傳入參數值

應用:兩組陣列合併在一起

以往大都使用 concat() 來合併兩個陣列,使用 (…) 可更靈活。

1
2
3
4
5
6
7
let europe = ['Paris', 'Milan', 'Berlin'];
let asia = ['Taipei', 'Tokyo', 'Hongkong'];

const allCity = [...europe, ...asia];
console.log(allCity); // ["paris", "Milan", "Berlin", "Taipei", "Tokyo", "Hongkong"]
const allCityEurope = ['London', ...europe, 'Lyon'];
console.log(allCityEurope); // ['London', 'Paris', 'Milan', 'Berlin', 'Lyon']

陣列展開語法與原本處理的方法比較,使用展開的寫法簡潔許多。

1
2
3
4
5
6
7
8
const values = [1, 2, 3];
const copiedValues = [...values];
// 等同於:
const values = [1, 2, 3];
const copiedValues = [];
for (let i = i; i < values.length; i++) {
copiedValues.push(values[i]);
}

在陣列中以展開加入資料或是,以 slice() 指定要取出的部份

1
2
3
4
5
6
7
8
9
// 前後加資料
const arr = ['a', 'b', 'c'];
const arr2 = ['z', ...arr, 'x'];

console.log(arr); // ['a', 'b', 'c']
console.log(arr2); // ["z", "a", "b", "c", "x"]

// 修改要展開的資料,用slice切片的方式
const arr3 = [...arr.slice(0, 2), 'hi', ...arr.slice(3)]; // ['a', 'b', 'hi']

應用:在物件,以展開加入資料

在物件裡也可以使用展開運算子來加入資料,需注意,如有相同的 key 則會有被覆蓋的風險,並取最後一個。
以下面的範例來說,bar 物件本身就有一個 key a, 在以 Spread operator 的方式將 bar, bar1 加入物件時,bar1, bar2 物件也有兩 key 為 a ,所以重複的會被覆蓋,而以最後一個為主。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const bar = {
a: 1,
b: 2,
c: 3,
};

const bar1 = {
a: 18,
d: 99,
};

const bar2 = {
a: 2, // 會被展開覆蓋掉
...bar,
...bar1,
a: 42, //
};

console.log(bar); // {a: 1, b: 2, c: 3}
console.log(bar2); // {a: 42, b: 2, c: 3, d: 99}

應用:淺層複製(shallow copy)

在操作陣列時,因為陣列為物件型別,以參考傳值的特性,所以複製的陣列如有修改,會改到原本的陣列。
為避免動到原始資料,可利用展開運算子來淺層拷貝。

1
2
3
4
5
6
7
8
9
10
11
12
13
const europe = ['paris', 'Milan', 'Berlin'];
const europe2 = europe;
europe2.push('Lyon');
console.log(europe); // ["paris", "Milan", "Berlin", "Lyon"]
console.log(europe2); // ["paris", "Milan", "Berlin", "Lyon"]

// 淺層拷貝作法
const europe = ['Paris', 'Milan', 'Berlin'];
const europe2 = [...europe];
europe2.push('Lyon');

console.log(europe); // ["Paris", "Milan", "Berlin"]
console.log(europe2); // ['Paris', 'Milan', 'Berlin', 'Lyon']

應用:把類陣列(Array-like)轉成純陣列

類陣列無法繼承陣列的所有方法,類陣列的產生有:從 DOM 抓取而成的陣列、arguments 物件,如果要對類陣列使用部份 Array 方法,需要先轉換成一般陣列。
可利用展開運算子來轉換。

1
2
3
4
const lists = document.querySelectorAll('li');
// 得到的 NodeList 是類陣列,無法使用部分陣列方法
listLiArr = [...lists];
// 轉換成一般陣列

應用:用來把某個陣列展開,然後傳入函式作為傳入參數值,例如加總函式的範例:

1
2
3
4
5
function sum(a, b, c) {
return a + b + c
}
const args = [1, 2, 3]
sum(…args) // 6

其餘運算子 (rest operator)

摘要

  • 其餘運算子 (rest operator)也可稱為其餘參數,也就是傳入的參數。
  • 函式可以使用arguments來取得參數,但是arguments為類陣列,但使用其餘運算子所取得的其餘參數則是陣列。
  • 其餘運算子會收集其餘的(剩餘的)值,將其轉成一個陣列。
  • 會用在函式定義時的傳入參數識別名定義(其餘參數, Rest parameters),以及解構賦值時。
  • 使用情境 1. : 在函式定義中的傳入參數定義中,稱之為其餘參數(Rest parameters)。
  • 使用情境 2. : 用在解構賦值時。

** 把其餘的都放在一起 **

應用:希望取前兩個各自給變數名稱,剩餘的放在另一個變數裡

1
2
3
4
5
const city = ['Taipei', 'Paris', 'Tokyo', 'Lyon', 'Tainan'];
const [capTaiwan, capFrance, ...autre] = city;

console.log(capTaiwan, capFrance); // "Taipei", "Paris"
console.log(autre); // ["Tokyo", "Lyon", "Tainan"]

其餘參數

1
2
3
4
5
6
function sum(a, b, c) {
return a + b + c;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2)); // NaN
console.log(sum(1, 2, 3, 4)); // 6

隨時都有可以加總更多或少於參數數量的數字,遇此狀況可利用其餘參數(…valus)來改寫此函數

1
2
3
4
5
6
7
8
9
function sum(...values) {
let total = 0;
for (let i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5));

其餘參數也可以和一般的函數參數使用,要注意一個函式只能有一個其餘參數,且只能寫在參數的最後一個位置

1
2
3
function (x,y, ...values) {
// do something
}

函式在接收參數時

情境:函式不確定會接收幾個參數,可使用其餘運算子的方式,讓參數無論是幾個,都會儲存成一個陣列

1
2
3
4
5
6
7
8
9
10
11
// 加總函式
function addTotal(...nums) {
// 參數成為陣列,也要以陣列的方式處理
let total = 0;
nums.forEach((element) => {
total += element;
});
return total;
}

addTotal(1, 2, 3, 4, 5, 6, 7); // 28

小練習

  1. 將 Paris 放到 europe 裡,其餘放在 asiaCity 裡
1
2
3
4
const city = ['Paris', 'Taipei', 'Tokyo', 'Tainan'];
const [europe, ...asiaCity] = city;
console.log(europe); // 'Paris'
console.log(asiaCity); // ['Taipei', 'Tokyo', 'Tainan']
  1. 把 food 和 drink 合併到 cafeShop 裡,並在中間加一項 beer
1
2
3
4
5
const food = ['cookie', 'cake', 'pizza'];
const drink = ['cafe', 'soda', 'the'];

const cafeShop = [...food, 'beer', ...drink];
console.log(cafeShop); // ['cookie', 'cake', 'pizza', 'beer', 'cafe', 'soda', 'the']

補充:

起手式的寫法

Vu2

1
2
3
4
5
6
7
8
9
10
11
12
13
let app = new Vue({
el: "#app",
data:{
todos: [
'todo1', 'todo2', 'todo3'
],
},
methods: {
deleteTodo: (index)=> {
app.todos.splice(index, 1)
},
},
});

Vue3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const app = Vue.createApp({
data(){
return {
todos: [
'todo1',
'todo2',
'todo3'
],
};
},
methods: {
deleteItem: function(index)=> {
this.app.todos.splice(index, 1)
},
},
})

const vm = app.mount('#app'); // 最後在綁定

TELEPORT

component 寫法不同

Vue2

HTML 裡的 <model-button> 為自定義標籤,HTML 內容則寫在model-buttontemplate裡。
這個 Modal 彈出視窗通常會放在 <body>附近,如果寫在 HTML 太裡面會不好調整, 如果使用 Teleport 則可以使用<teleport to="body">語法,將這個 Modal 放到 </body> 前面。

1
2
3
4
5
6
7
8
9
<body>
<div style="position: relative;">
<h3>cTooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
<!-- 使用 Teleport 會將 template 放到這裡來。 -->
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .Vue.extend({
app.component('modal-button',{
template: `
<button @click="modalOpen" = true>
Open full screen modal!
</button>

<div v-if="modalOpen" class="modal">
<div>
I'm a modal
<button @click="modalOpen" = false>
Close
</button>
</div>
</div>
`,
data(){
return {
modalOpen: false,
}
}
})

Teleport 寫法與用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Vue3寫法
template: `
<button @click="modalOpen" = true>
Open full screen modal! (With teleport!)
</button>

<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleport modal (my parent is "body")
<button @click="modalOpen" = false>
Close
</button>
</div>
</div>
</teleport>
`,
data(){
return {
modalOpen: false,
}
}

Composition API

Option API 的寫法叫零散,而 Composition API 寫法適合較大的專案,把同樣性質的碼放在同一區塊裡,讓專案更好管理。

KEY

Vue2

在 Vue2 時頗常使用 KEY 的寫法,來區分哪個 input

1
2
3
4
5
6
7
8
9
// Vue2 寫法
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>

Vue3

不需寫 key 讓程式碼更精簡,Vue3 會自動判斷。

1
2
3
4
5
6
7
8
9
// Vue3 寫法
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>

在 Template 寫法

Vue2

需要寫在每個標籤上

1
2
3
4
5
// Vue2
<template v-if="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>

Vue3

1
2
3
4
5
// Vue3
<template v-if="item in list" :key="item.id">
<div >...</div>
<span >...</span>
</te

ARRAY CHANGE DETECTION

通常 Data 裡面的資料只要一被更動,就會馬上畫面上更動呈現,但在 Array 的部份,並沒這麼聰明,當我們更改資料裡的陣列時, Vue2 不會立即反應至畫面,解決方法是在以 set 的方式來解決。

Vue2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var vm = Vue({
data: {
items: ['a', 'b', 'c'],
}
})
vm.item[1] = "x"; // is NOT reactive
vm.item.length = 2; // is NOT reactive
// --------------
// 以 Vue.set來解決
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
Vue.items.splice(indexOfItem, 1, newValue)

Vue.$set(vm.items, indexOfItem, 1, newValue)
1
2


Vue3

會直接修改畫面,而不需使用 set 範例

V-FOR With V-IF

在Vue 裡 V-FORV-IF 混在一起寫的狀況是非常不推薦的。
在 Vue2 如把兩者寫在一起 <v-for> 會優先,再檢查<v-if> 有沒有成立,而 Vue3 則相反,會先檢查 <v-if> 條件有沒成立,再去跑<v-for> 迴圈。

1
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>

如果不想記執行順序,最好還是多寫一層會比較清楚。

1
2
3
<template v-for="todo in todos">
<li v-if="!todo.isComplete"> {{ todo }} </li>
</template>

KEY CODE

KEY CODE 是鍵盤的對應代碼,例如 13 = enter,在 Vue3 裡已經棄用此方法,而直接以 Key modifiers 方式寫鍵盤名稱。

1
2
3
4
5
6
7
<!-- vue2 寫法 -->
<input v-on:keyup.13="submit">

<!-- only call `vm.submit()` when the `key` is `Enter` -->
<input @keyup.enter="submit" />
<!-- converting key names to kebad-case -->
<input @keyup.page-down="onPageDown" />

Key Aliases

.enter, .tab, .delete(captures both "Delete" and "Backspace" keys), .esc, .space, .up, .down, .left, .right

CONPONENT

Vue2

component 在 Vue2 的寫法是用 vue 的物件去宣告,切需要將內容包在一個 div(html root) 裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Define a new component called button-counter
Vue.component('button-counter', {
data: function(){
return {
title: "title",
content: "<p>content...</p>"
}
},
template:`
<div class="blog-post">
<h3>{{title}}</h3>
<div v-html="content"></div>
</div>
`
})
new Vue({ el: '#components-demo'})

Vue3

component 在 Vue3 的寫法會先createApp({})再去宣告一個 component。且內容物不需再以 div 包起來。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// create a Vue application
const app = Vue.createApp({})

// Define a new global component called counter
app.component('button-counter', {
data(){
return: {
title: "title",
content: "<p>content...</p>"
}
},
template:`
<h3>{{title}}</h3>
<div v-html="content"></div>
`
})

app.mount('#components-demo')

V-MODEL ON COMPONENT

1
2
3
4
<input v-model="searchText" />

<!-- 綁定資料 / 監聽 -->
<input :value="searchText" @input="searchText = 4event.target.value" />

在 component 裡綁定的方法也不太相同

Vue2

Vue2 的 v-model 是綁定 value,綁定的事件是 input

1
2
3
4
5
6
7
8
9
10
11
12
<custom-input v-bind:value="searchText" v-on:input="searchText = $event"></custom-input>

// component
Vue.component('custom-input', {
props: ['value'],
templete:
`
<input
v-bind:value="value",
v-on:input="$emit('input', $event.target.value)"
`
})

Vue3

Vue3 的 v-model 綁定的是 model-value 與 update 的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
<custom-input v-bind:model-value="searchText" @update:model-value="searchText = $event"></custom-input>

// component
app.component('custom-input', {
props: ['modelValue'],
emits:['update:modelValue'],
templete:
`
<input
:value="modelValue",
@input="$emit('update:modelValue', $event.target.value)"
`
})
1
<custom-input v-model="searchText"></custom-input>

DOM TEMPLATE PARSING CAVEATS (DOM模板解析的注意事項)

有時想要在一個

塞入一個元件,但因為瀏覽器解析沒有表格標籤trtd就會報錯,所以 vue.js 在 table 同樣置入一個<tr>的標籤,並在標籤上塞入元件名稱,來看看 vue2, 3的差異。

1
2
3
4
<!-- 這樣寫會出錯 -->
<table>
<blog-post-row></blog-post-row>
</table>

Vue2

1
2
3
<table>
<tr is="blog-post-row"></tr>
</table>

Vue3

1
2
3
4
<table>
<!-- 不可直接寫元件名,需用單引號包起來 -->
<tr iv-s="'blog-post-row'"></tr>
</table>

REMOVE $on / $off / $once (棄用)

原本是以 $on / $off / $once 來湊出新的架構為: EVENBUS 來當作各元件之間的橋樑
現在作法會先開一個空的 Vue 並說明客製化一個名為 custom-event ,當event發生時會做一些事情。

Vue2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// eventHub.JS
const eventHub = new Vue()
export default eventHub

// ChildComponent.vue
import eventHub from './eventHub'

export default {
mounted() {
// adding eventHub listener
eventHub.$on('custom-event', ()=>{
console.log('Custom event triggered');
})
},
beforeDestroy() {
// removing eventHub listener
eventHub.$off('custom-event')
},
}

// ParentComponent.vue
import eventHub from './eventHub'

export default {
methods:{
callGlobalCustomEvent(){
eventHub.$emit('custom-event')
// if ChildComponent is mounted, we will...
}
}
}

eventBus 被棄用後,替代方案為 Vuex

Vue Cli / Vite

Vue Cli 以下指令的方式創建專案,使用webpack
Vite 以下指令的方式創建專案,但渲染頁面速度更快,因為使用瀏覽器內建系統。

Vue2

Vue3

Vue2

Vue3

Vue2

Vue3

有很多時候,我們只會拿這放大鏡看,而忘了抬起頭來,退後幾步讓視野看得更多更廣。有時候 coding 就是這麼一回事。

如何開始一個小專案

想要做一個小小志工排班管理系統,卻不知如何開始,花了許多時間在看線上教學,但自己實作時卻遇上許多問題,如何開始是一個大問題,接著網頁要放哪裡?要不要有登入機制?資料要放哪裡?開放哪些權限?這些只是大方向,接著就會陷入糾結,例如用哪個框架?(其實比較會的也只有Vue),以 CDN 或 CLI 實作?要用自己的資料庫還是線上的(比較熟mySql,但是PHP有點忘了?),就這樣反覆折騰之後,想說那就用 VueJs 和 Firebase 搭配吧?接這就花些時間熟悉 Firebase 所提供的服務,且看來在自己還不會後端自己開 API 之前,Firebase 似乎是比較適合。

Firebase

看來 firebase 的功能不少,如果熟悉應該會對開發小專案有用,十幾年前開始使用 MySql 也真的習慣「關連式」的資料結構,firebase 使用的是 collection 的資料結構,我把它想成想是一個物件型態,這樣或許比較好理解。

collection 的資料結構有個好處就是想加資料就加資料,彈性很大。先把需要的資料大概的列出來,在 firebase 設定好,再去設定連線資料,回到開發環境,就可以連上去了。firebase 也提供 CLI 工具,讓我們在本地開發更方便,且可以直接將專案推上 Github 真的蠻不錯的。