---
# System prepended metadata

title: Vue.js 筆記
tags: [vue.js]

---


# Vue.js 學習筆記
:::warning
參考 [**30天手把手的vue.js教學!**](https://ithelp.ithome.com.tw/users/20129072/ironman/3052)、[**重新認識 Vue.js**]([https://](https://book.vue.tw/menu.html)) 做的學習筆記
:::

## 目錄
[TOC]
## ▊ 簡介
Vue基於標準的HTML、CSS和Javascript，提供了一套宣告式、元件化的模型。

**Vue的特色：**
1. **宣告式渲染**
2. **MVVM架構**
3. **漸進式框架**

### ▎宣告式渲染 vs 指令式渲染
**指令式渲染：**
1. 一步一步的告訴程序怎麼做
2. code之間會相互依賴，缺了一部份程式碼可能就不能動了

**宣告式渲染：**
1. 只專注想完成的部分，其他交給JS物件管理
2. code之間不會相互依賴

:writing_hand: **範例**
例如，當在input欄輸入文字後，下面的`<p>`段落要立即顯示輸入的文字。
:::warning
如果使用Vue(宣告式)的作法，只需要：
* 使用`v-model`綁定input欄位
* 在`＜p>`裡使用`{{v-model綁定的資料}}`的寫法
:::
:::info
如果是指令式的做法：
* 用`.addEventListener()`監聽input事件
* 當input事件觸發後，用`document.querySelector`等語法抓取`<p>`的DOM，並將input內容抓出來
* 將抓到的文字寫進`<p>`
:::

---

### ▎MVVM
M(Model)、V(View)、VM(ViewModel)
![](https://hackmd.io/_uploads/HJt8zqBAn.png)
Vue 包辦了監聽和資料綁定，畫面 (View) 觸發事件或是狀態 (Model)變更了，都會由 Vue (ViewModel)來處理並且同步更新畫面 (View)。

## ▊ Vue實體
每創建一個vue檔案，其實是在創建一個**vue實體**，原本的code如下：
```javascript=
new Vue({
  el: '#app',
  data: {
   greeting: 'Hello World!',
   user: 'Hassan Djirdeh',
   city: 'Toronto',
  },
});

```
vue實體中有眾多屬性，例如：data、methods、computed...。

### ▎data屬性
當一個vue實體被建立時，會將在data物件中的所有屬性加入vue的響應系統中(eactivity system)，**當data物件中的任何屬性被改變值，我們的畫面(view)會即時重新渲染**，讓畫面的內容符合新變動的值。

在vue的`<template>`中可以利用 `{{ }}` (mustache syntax)來使用data物件中的屬性，範例如下：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <h1>{{message}}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Welcome to Vue!'
    };
  }
};
</script>
```
:::

>* **`export default`**：導出vue.js。
>* **`data()`**：定義data物件中的數據，data物件中的message的值會取代template中的{{message}}，如下圖，畫面會顯示Welcome to Vue!。
![](https://hackmd.io/_uploads/SkAKYDOA2.png =200x)


---

### ▎methods屬性
vue實體中methods屬性能定義綁在實體上的**函數**，就像一般的js函數，下面是一個簡單的[範例](https://codepen.io/nojdeluj-the-looper/pen/VwqmVpx)：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <h1>{{message}}</h1>
    <button v-on:click="greet">Click Me</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Welcome to Vue!'
    };
  },
  methods:{
    greet(){
      alert('Hello!')
    }
  }
};
</script>
```
:::

>* **`greet()`**：methods中定義了一個greet函數，此函數會跳出一個alert。
>* **`v-on:click="great"`**：在button監聽點擊事件，當點擊按鈕時會觸發greet()函數，所以會如下面的畫面跳出alert。
![](https://hackmd.io/_uploads/B1wUYvOC2.png =400x)


可以理解為：
```javascript
btn.addEventListener('click', greet)
```
`v-on`還可以縮寫為`@`：
```html
<button @click="greet">Click Me</button>
```

---

### ▎computed屬性
一般是用來處理運算後才會顯示在`<template>`上的資料，可以參考下面的[範例](https://codepen.io/nojdeluj-the-looper/pen/WNLRbgE)：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div>
    <label for="firstname">First name</label>
    <input type="text" name="firstname"  v-model="firstName">
    <label for="lastname">Last name</label>
    <input type="text" name="lastname" v-model="lastName">
    <h2>{{fullName}}</h2>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    };
  },
  methods: {
    
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
};
</script>

```
:::
 
> * 結果：
> ![](https://hackmd.io/_uploads/r1TVBnL03.png =500x)
> * `computed`一定要`return`某個值

#### ▏methods vs computed
methods其實也能辦到相同的功能
```javascript
methods: {
    getFullName() {
      return `${this.firstName} ${this.lastName}`
    }
},

computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
}
```
:::info
兩者的差別在於：
* method是每次呼叫就會重新計算
* computed只有在資料變化時才會重新計算
:::
:::success
兩者使用的原則：
* method用在事件觸發的handler 或 vue實體中的函數上
* computed用在data屬性中，作為資料的延伸操作、處理 (例如排序)
:::

---

### ▎watch屬性
在vue實體中用來檢查某個屬性的值是否有發生任何變動，如果發生變動則做出對應的操作。
```javascript
watch: {
   dataName: function(val, oldVal) {
     // 要執行的callback
   }
}
```
> * `dataName`：可以是data屬性的變數 或 computed屬性計算後的值
> * `val`：第一個參數會是變動後的值
> * `oldval`：第二個參數會是變動前的值

<br>

若要監聽的是某個物件的值，則需要監聽它的key值，並且以 **字串** 的方式傳入。
```javascript
data() {
    return {
      dataName: {
        key1: 'abc',
        key2: 123
      }
    };
},

```
```javascript
watch: {
   "dataName.key1": function(val, oldVal) {
     // 要執行的callback
   }
}
```
例如：陣列的長度
```javascript
"arr.length" : function(val) {
      console.log(`the new length is ${val}`)
}
```
<br>

若要傳入options，則需要以物件的方式表示，其中包含：
* handler function
* options (包含immediate & deep，預設為false)
```
watch: {
    dataName: {
      handler(val, oldVal) {
        
      },
      immediate: true,
      deep: true
    }
}
```
> * **`immediate`**：若設為true，則handler在初始化時會先執行一次。
> * **`deep`**：若設為true，則會一同監測這個值的深層有沒有變化，也就是監聽整個`dataName`物件，物件內的任何值發生變化都會觸發handler。

## ▊ vue directives 指令

常見的指令有：`v-on`, `v-model`, `v-bind`, `v-if`, `v-show`, `v-for`，透過這些指令可以輕鬆完成監控事件、同步資料或渲染資料。

## ▊ v-on | event handler
```htmlmixed
<h1 v-on:click="method">Click me!</h1>
<h1 v-on:dblclick="method">Double Click me!</h1>
<form v-on:submit="method">...</form>
<input v-on:keydown="method"
placeholder="Press down on keys" />
<input v-on:keyup="method"
placeholder="Release keys" />
```
### 縮寫和inline寫法
v-on也提供**縮寫**和**行內**(inline)寫法
```htmlembedded
// 原始寫法
<h1 v-on:click="method">Click me!</h1>

// 縮寫
<h1 @click="method">Click me!</h1>

// 縮寫 & inline-javascript
<h1 @click="isAdmin = true">Click me!</h1>
```
### ▎event modifiers
vue也提供各種**修飾符**(event modifiers)做更精確的事件監聽處理。
例如寫原生js使用到的`event.preventDefault()`或`event.stopPropagation()`在vue中可以使用修飾符達到一樣的效果。
* `event.preventDefault()`和`event.stopPropagation()` -> [參考教學](https://ithelp.ithome.com.tw/articles/10198999)
    * `event.preventDefault()`：停止 事件的默認動作，例如`<a>`觸發的事件中加入，就不會觸發原本跳轉連結的動作。
    * `event.stopPropagation()`：可以停止事件，避免一個事件觸發多個事件。
```htmlembedded
<!-- 下方的點擊事件將不會繼續傳播 -->
<a v-on:click.stop="doThis"></a>

<!-- 下方的submit事件將不會刷新頁面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾符是可以彼此串接的 -->
<a v-on:click.stop.prevent="doThat"></a>
```
### ▎key modifiers
vue還提供了特殊按鍵的監聽修飾符(Key Modifiers)，讓我們可以簡單的根據使用者按下的key去決定觸發的事件。
```htmlmixed
<!-- 下方的點擊事件將會由enter按鍵觸發 -->
<input v-on:keyup.enter="submit">

<!-- 方的點擊事件將會由Alt + C 觸發-->
<input v-on:keyup.alt.67="clear">
```
## ▊ v-bind | 屬性綁定
1. **透過v-bind可以改變html的屬性**，例如：
```javascript=
<template>
  <div id="app">
    <img v-bind:src="imgSrc"> //透過v-bind改變img的src屬性
  </div>
</template>

<script>
export default {
  data() {
    return {
      imgSrc: './XXX.jpg'
    };
  }
};
</script>
```
2. **也可以綁定style(以物件形式接收)**，例如：
```html
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
```
```javascript
data: {
  activeColor: 'red',
  fontSize: 30
}
```
3. **針對clss做動態綁定(以陣列形式接收)**，例如：
```htmlembedded
<div v-bind:class="[activeClass, errorClass]"></div>
```
```javascript
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
```
以上的程式碼最終會render出以下的元素
```htmlembedded
<div class="active text-danger"></div>
```
還可以配合條件式決定綁哪個class，例如：
```htmlembedded
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
//根據isActive決定要不要綁activeClass，errorClass則必會綁定。
```
### ▎縮寫
同樣的可以縮寫
```htmlembedded
<img :src="imgSrc">
```

## ▊ v-if & v-show | 條件渲染
這是vue兩種常用的條件渲染方式，可以使用data內的屬性來控制`<template>`中的元素會不會出現，下面是[範例](https://codepen.io/nojdeluj-the-looper/pen/ExGNOvX)：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <p v-if="isLogin">我有登入!</p>
    <p v-show="isAdmin">我是管理員</p>
    <button @click="isAdmin = !isAdmin">點我切換管理員身分</button>
<!--       v-on的縮寫和inline寫法 -->
  </div>
</template>

<script>
export default {
  data() {
    return {
      isAdmin: false,
      isLogin: true
    };
   }
 }
</script>
```
:::
> * 結果
![初始](https://hackmd.io/_uploads/rkLotsS0n.png =200x) ![點一下後](https://hackmd.io/_uploads/B1K6tsHRn.png =210x)

### ▎v-show vs. v-if
* `v-show`是單純控制css display屬性，效能會比較好。
* `v-if`可以用在`<template>`(子元件)的顯示上；`v-show`不能。
* `v-if`可以搭配`v-else-if`、`v-else`使用。
```htmlembedded
<template>
  <div id="app">
    <p v-if="isLogin">我有登入!</p>
    <p v-else>我沒登入喔喔喔喔喔!</p>
  </div>
</template>
```
:::warning
**注意**：`v-else`要緊接在`v-if`後面，不然會顯示錯誤
:::

## ▊ v-for | 渲染複數元件
v-for指令可以利用你存放在data屬性內的值做元件渲染，下面是[示範](https://codepen.io/nojdeluj-the-looper/pen/WNLoYZN)：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <h1>V-for demo</h1>
    <ul>
      <li v-for="item in items" :key="item.id">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: ["Learn vue","Buy diner", "Make a todo list"]
    };
  }
};

```
:::
> * `v-for="item in items"`的item可以自由命名，後方是data屬性中的陣列/物件。
> * 使用`v-for`時，務必利用`v-bind`來綁定key值 (`:key="item.id"`)，key值的目的是清楚分辨每一個`v-for`render出的元素。
> * 結果會render出三個`<li>`元素
> ![](https://hackmd.io/_uploads/Hks1-2BAn.png =200x)

也可以用下面的寫法：
```javascript
<li v-for="(item, index) in items" :key="item.id">{{index}}: {{item}}</li>
```
> * `index`可以自由命名
> * 結果會是
> ![](https://hackmd.io/_uploads/Sk7-zhB03.png =230x)

也可以用在物件上，使用慘數value、key、index，示範如下：
:::spoiler **打開查看程式碼**
```javascript=
<template>
    <div id="app">
        <h1>V-for demo</h1>
        <ul>
            <ul>
                <li v-for="(value,key,index) in person">
                  {{key}} : {{value}}
                </li>
            </ul>
        </ul>
    </div>
</template>

<script>
export default {
  data() {
    return {
      person: {
        name:'Danny',
        age: 29,
        gender: 'male'
      }
    };
  }
};
</script>
```
:::
> * 結果
> ![](https://hackmd.io/_uploads/HkxdNhrC2.png =200x)

## ▊ v-model | 雙向綁定
### ▎單向綁定 vs. 雙向綁定
#### ▏單向綁定
`v-bind`就是單向綁定，改變vue實體的值會反應在view，但無法反過來。
#### 雙向綁定
當畫面或資料有更新，對方也會隨之更新，例如`v-model`，下面是[範例](https://codepen.io/nojdeluj-the-looper/pen/dywOQVK)：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <h1>{{message}}</h1>
    <input type="text" v-model="message">
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: '你好'
    };
  }
};
</script>

```
:::
> * 使用`v-model`對message進行雙向綁定，不管是修改input欄位的值或是修改vue實數的值，都會產生對應的變化
> * 結果
> ![](https://hackmd.io/_uploads/ByE1_2BRn.png =200x)

#### ▏練習：利用v-model模擬表單送出
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <div class="form-container">
      <form>
        <h2>模擬送出表單</h2>
        <div class="input-group">
          <label for="username">請輸入帳號: </label>
          <input type="text" name="username" v-model="username" v-focus>
        </div>
        <div class="input-group">
          <label for="username">請輸入密碼: </label>
          <input type="text" name="username" v-model="password">
        </div>
        <button @click.prevent="handleSubmit">送出</button>
      </form>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    handleSubmit() {
      alert(`你所輸入的帳號是: ${this.username} \n而你所輸入的密碼是${this.password}`);
    }
  },
  directives: {
    focus: {
      inserted: function (el) {
        el.focus()
      }
    }
  }
};
</script>
```
> * `v-focus`為vue實體中自定義的directives
> * 結果
    > ![](https://hackmd.io/_uploads/SyCRu3BC2.png)
:::

## ▊ 自定義directives
寫法如下：
```javascript
directives: {
  // 你的客製化指令名稱
  focus: {
    // 你想在哪個時間點執行什麼操作
    inserted: function (el) {
      el.focus()
    }
  }
}
```


> directives屬性中提供了各種hook，可以設定什麼時候操作指令：
> * **`bind`**：只在指令被綁到指定元素時，執行一次傳入的callback
> * **`inserted`**：當綁定元素被插入DOM中就執行傳入的callback
> * **`update`**：當包含綁定元素的VNode被更新時，執行傳入的callback

> 傳入的callback的參數：
> * **`el`**：綁定的元素
> * **`binding`**：包含指令名稱、值和傳入參數的物件
> * **`vnode`**：vue產出的虛擬節點

## 中間練習
學習完Vue的屬性和directives後，來做一個練習題：[實作一個計算機](https://hackmd.io/@broodfish/HJx-h3HAh)！

## ▊ Vue Lifecircle Hooks
![](https://hackmd.io/_uploads/rJoFfaUAh.png)
紅框為主要的階段 (Hooks)，其中包括：
* beforeCreate
* created
* beforeMount
* mounted
* beforeUpdate
* updated
* beforeDestroy
* destroyed

另外還有用於keep-alive標籤的兩個階段：
* activated
* deActivated

### ▎不同hook該做的事
我們需要在特定的階段執行特定的操作，避免出錯，例如載入前運行loading動畫、載入後執行某個函數...等。

* **beforeCreate**
    * 初始化Vue實體、元件之後，數據觀測和event/watcher事件前。
    * 例如：加入loading動畫
* **created**
    * 實體、元件建立完成，建立的屬性也綁定到該實體，可以讀取data內的值。
    * DOM元素尚未完成，且`$el`元素還不存在。
    * 例如：結束loading，做初始化來實現函數執行
* **beforeMount**
    * Vue實體被掛載之前的階段，`$el`尚未存在。
* **mounted**
    * 建立`$el`，並掛載至創建的Vue實體，產生出頁面。
    * DOM元素已產生，可以操作元素。
    * 例如：通常在mounted 或 created階段向後端發出API請求。
* **beforeUpdate**
    * 在數據被更新前的階段，數據已經更新，但DOM還沒渲染。
* **updated**
    * 數據更新導致DOM重新渲染，並且View為重新渲染的結果。
* **beforeDestroy**
    * Vue實體要被銷毀前的階段，此階段實體還可以使用。
    * 例如：做最後的詢問
* **destroyed**
    * 銷毀Vue實體，實體也會解除所有綁定，子實體也會被銷毀。
    * 例如：清空相關的內容
* **activated**
    * 若HTML標籤內有設定keep-alive，就會觸發這階段的函數，並跳過destroy階段。
* **deActivated**
    * 停用keep-alive時會觸發的階段。

### ▎呼叫hook
寫法和vue屬性一樣
```javascript
<script>
export default {
  data() {
    return {
        
    };
  },
  methods: {
      
    }
  },
  beforeCreate(){
      
  },
  created() {
      
  }
};
</script>
```

## ▊ Vue components
Vue提供了元件化的概念，讓網頁有良好的結構，以此方便管理，每一個Vue元件其實就是一個Vue實體。

data, methods, computed, watch等屬性在元件中也能使用

使用下列方式註冊Vue元件：
```javascript
Vue.component('元件名稱', {
  傳入的選項
}); 
```
<br>

例如重複的內容可以把它包裝成一個元件，下面的範例是將重複出現的todo-list包裝成一個global的元件：
:::spoiler **打開查看程式碼**
```javascript=
<template>
  <div id="app">
    <h1>My Todo List</h1>
    <todo-list v-for="todo in todos" :key="todo.id">
    </todo-list>
  </div>
</template>

<script>
const todos = [
  {
    title: "Get dressed",
    isComplete: false
  },
  {
    title: "Buy food",
    isComplete: false
  },
  {
    title: "Eat lunch",
    isComplete: true
  },
  {
    title: "Write Article",
    isComplete: true
  }
];
  
Vue.component('todo-list',{
  template:
  `
    <div class="todo-wrapper">
    <div class="todo-title">
      test
    </div>
    <div class="todo-icons">
      <i class = "fa fa-check" aria-hidden="true"></i>
      <i class = "fa fa-trash-alt" aria-hidden="true"></i>
    </div>
  </div>
  `
});

export default {
  data() {
    return {
      todos: todos
    };
  },
  methods: {}
};
</script>
```
:::
> 可以減少重複出現的todo-list格式，接下來需要將資料傳進todo-list這個子元件
> -> **使用props屬性來接收父層的資料傳遞進子層**

---

### ▎props屬性
設定props屬性可以讓這個元件接受來自父層傳入的資料，完整的props分為幾個部分：
1. 傳入的props名稱
2. 傳入的props資料型態
3. 傳入的選項

寫法如下：
```javascript
props: ['paramA', 'paramB']
```
<br>

也可以使用物件形式，給傳入的資料增加一些選項：
```javascript
props: {
    paramA : {
        type: Object, // 指定傳進來的參數必須是物件，否則報錯
        required: true, // 此欄位必填
        default: {} // 預設值為空物件
    } 
  }
```
> * **`type`**：指定此參數的type
> * **`required`**：此參數是否必須傳入
> * **`default`**：此參數的預設值

<br>

所以上面的範例加上props後會如下：
:::spoiler **打開查看程式碼**
```javascript=
Vue.component('todo-list',{
  template:
  `
    <div :class="[todo.isComplete? 'success':'error','todo-wrapper']">
    <div class="todo-title">
      {{todo.title}}
    </div>
    <div class="todo-icons">
      <i class = "fa fa-check" aria-hidden="true"></i>
      <i class = "fa fa-trash-alt" aria-hidden="true"></i>
    </div>
  </div>
  `,
  props: {
    todo: {
      type: Object,
      required: true
    }
  }
});
```
:::

<br>

寫好props接收資料後，再使用 **`v-bind`** 將父層資料傳遞到子元件，寫法如下：
```javascript
v-bind: paramA="item"
```
> * `paramA`：props裡設定好接收資料的參數
> * `item`：要傳遞的資料

<br>

所以範例中`<template>`的todo-list要加上`v-bind`，寫法如下：
:::spoiler **打開查看程式碼**
```javascript=
<todo-list v-for="todo in todos" :key="todo.id" :todo="todo"></todo-list>
```
:::

<br>

最後[範例](https://codepen.io/nojdeluj-the-looper/pen/xxmgwPX)的結果如下：
![](https://hackmd.io/_uploads/r1vFi1wR3.png =250x)

---

### ▎emit | 客製事件
#### ▏emit & on
:::danger
Vue 3 實體不提供`emit`和`$on`函數了，在Vue 3使用請[參考此文章](https://juejin.cn/post/6890781300648017934)。
:::
* emit可以建立一個全新的事件，例如click, load, change等事件。
* on則是監聽到該事件時，該做什麼事。

子層觸發事件的寫法：
```javascript
// 子層元件
this.$emit('eventName') // 向上層傳一個叫做eventName的事件
```
父層監聽到事件的處理：
```javascript
// 父層
<componentName v-on:eventName="負責處理這事件的函數"></componentName>

...

methods: {
  負責處理這事件的函數() {
    // 略
  }
}
```

<br>

若需要觸發事件並傳送資料到父層，可以將資料寫在第二個參數，寫法如下：
```javascript
this.$emit('eventName', {
  paramA: 'abc'  
})
```

<br>

父層 & 子層之間的資料流動如下：
```
     props傳遞資料
    ------------> 
父層              子層
    <------------
    emit傳達客製事件
```
> 子層是沒辦法直接影響父層的資料的

<br>

接續上面的todo-list範例，在子層加一個delete-todo事件，父層間聽到後刪除toods裡的資料，並重新render：
:::spoiler **打開查看程式碼**
```javascript=
Vue.component('todo-list',{
  template:
  `
    <div :class="[todo.isComplete? 'success':'error','todo-wrapper']">
    <div class="todo-title">
      {{todo.title}}
    </div>
    <div class="todo-icons">
      <i class = "fa fa-check" aria-hidden="true"></i>
      <i class = "fa fa-trash-alt" aria-hidden="true" @click="deleteTodo"></i>
      // 在子層綁一個v-on，當點擊時觸發deleteTodo
    </div>
  </div>
  `,
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: {
    deleteTodo() {
      this.$emit('delete-todo', { //emite一個叫delete-todo的事件
                 title: this.todo.title //並回傳todo.title
                 })
    }
  }
});
```
:::

<br>

接著在父層監聽子層的事件：
:::spoiler **打開查看程式碼**
```javascript=
<todo-list v-for="todo in todos" :key="todo.id" :todo="todo" @delete-todo="handleDeleteTodo">
    </todo-list>

...

methods: {
    handleDeleteTodo(payload) {
      console.log(payload.title);
      this.todos = this.todos.filter(item => item.title !== payload.title)
    }
  }
```
:small_red_triangle_down: handleDeleteTodo可以再精簡
```javascript=
methods: {
    handleDeleteTodo({ title }) {
      console.log(title);
      this.todos = this.todos.filter(item => item.title !== title)
    }
}
```
:::

[範例的demo](https://codepen.io/nojdeluj-the-looper/pen/GRProMJ)

---

### ▎slot
slot可以讓元件內有一個插槽，可以插入內容，讓元件更泛用。

#### ▏用法
宣告一個子元件結構，內含slot：
```javascript
Vue.component("compName", {
  template: `
  <div>
    <slot></slot>
  </div>
  `,
});
```
父層使用此元件：
```
<template>
  <div>
    <compName>
        <h1>TEST1234</h1>
    </compName>
    <compName>
        test5678
    </compName>
  </div>
</template>
```
父層渲染後的結果會長這樣
![](https://hackmd.io/_uploads/HJ3c9gwC3.png =180x)
渲染完的DOM結構長這樣：
```javascript
<div>
    <div>
        <h1>TEST1234</h1>
    </div>
    <div>
        test5678
    </div>
</div>
```

#### ▏named slot 命名插槽
一個元件可以有多個slots，透過`name`屬性對每個slot命名，父層就可以在指定的槽插入內容。

子層寫多個slots：
```javascript
Vue.component("compName", {
  template: `
  <div>
    <slot name="name1"></slot>
    <slot name="name2"></slot>
    <slot></slot>
  </div>
  `,
});
```
父層使用下面的寫法指定元件的slot槽：
```javascript
<template>
  <div>
    <compName>
        <template #name1>
            <h1>test1</h1>
        </template>
        <p>test2</p> //沒指定會插入slot預設位置
        <template #name2>
            <p>test3</p>
        </template>
    </compName>
  </div>
</template>
```
渲染結果如下：
![](https://hackmd.io/_uploads/Bkoz6xP03.png =150x)
:::warning
* 若父層指定了特定的slot name，但子層沒有對應的slot name的話，則會無法插入。
* 若父層沒有指定slot name，但子層沒有預設的(沒命名的)slot，不管還有沒有slot沒插入，都會無法插入。
:::

## ▊ Single File Components (SFC)
SFC是一個完整的vue檔案，每個vue檔案就是一個完整的元件，包含三部分：
1. HTML (template)
2. Javasript (script)
3. CSS (style)

```javascript
<template>
    <div>
        <!--HTML-->
    </div>
</template>

<script>
    // JAvaScript
</script>

<style>
    /*CSS*/
</style>
```

### ▎WHY use SFC?
用global component的方式會有幾個問題：
* 全域命名，所以元件名稱不能重複，當專案規模很大時會很麻煩。
* 不支援CSS
* template使用字串方式撰寫，閱讀和編寫不易。

=> 使用SFC可以解決這些問題，SFC還支援webpack打包建置的服務

### ▎`<template>` 特性
#### ▏只能用一個根元素 (root element)
必須長這樣，所有內容要用一個容器包起來：
```javascript
<template>
    <div>
        <!--略-->
    </div>
</template>
```
#### ▏template預設的markup為HTML
若要使用其他樣板，可以使用`lang`屬性改寫：
```javascript
<template lang="pug">
    div
      Hello world
</template>
```
### ▎`<script>` 特性

#### ▏一個SFC只能有一個`<script>`
#### ▏最終要輸出成一個Vue實體
#### ▏使用`require`/`import`來引入外部的js檔案
```javascript
import dayjs from 'dayjs' // 引入npm套件
import {helper} from '../utils.js' // 以相對路徑引入自己專案內的js檔案

export default {
  data() {
    return {
      
    };
  },
  methods: {
  
  },
  computed: {
  
  },
  mounted() {
  
  }
};
</script>
```

### ▎`<style>` 特性
#### ▏style預設是全域管理
若只在此元件中套用style，可以加入`scoped`屬性

``` javascript
<style scoped>
    /*CSS*/
</style>
```

#### ▏使用`lang`套用預處理器
像是`scss`或`stylus` (需安裝對應的預處理器和loader)
```javascript
<style  lang="stylus" scoped>
    /*CSS*/
</style>
```

### ▎在SFC中套用其他元件
在`<script>`內使用`import`載入指定的元件：
```javascript
import ComponentA from '../src/components/A.vue' 
```

```javascript
//A.vue
export default {
  components: {
    ComponentA
  }
};
```
這樣在`<template>`就可以使用該元件：
```
<template>
    <div>
        <component-a></component-a>
    </div>
</template>
```
:warning: :warning: :warning: 不管原本是怎麼命名的，要在`<template>`使用須轉為kebab-case、小寫，並以`-`相連。
:::warning
MyApp 轉為-> my-app
componentA 轉為-> component-a
:::

## ▊ vue-cli
接下來使用vue-cli練習，[vue-cli環境建立方法](https://hackmd.io/@broodfish/HybyuZPA2)。

## ▊ EventBus
`props`和`emit`只能做父層與子層之間的資料傳遞，做不到子層與子層之間的溝通，所以需要使用**EventBus**的概念。

![](https://hackmd.io/_uploads/ByJUZzPAn.png)
EventBus會做為所有元件的客製事件的監聽者，以此達到子層之間的溝通。

### ▎Vue 3
因為Vue 3不提供`emit`, `$on`, `$off`等函數，所以改用`mitt`套件。------------[教學1](https://www.casper.tw/development/2020/12/15/vue-3-mitt/) [教學2](https://juejin.cn/post/6957965225471508493)

## ▊ store
store的概念是所有資料的存放與變動都需要在管理資料的倉庫中完成，不能在其他元件變動倉庫內的資料。

## ▊ Vuex
Vuex分為`state`、`mutations`、`actions`等屬性
![](https://hackmd.io/_uploads/HyP8g-O03.png)
> 流程：
> * Components 透過State內的資料render出畫面
> * 資料需要變動時，透過Actions commit一個Mutation
> * Mutation 變更 State中的資料
> * Components 重新render變化後的結果

### ▎在vue-cli專案中加入vuex套件
在終端機輸入：`vue add vuex`，安裝vuex套件。
> `vue add 套件名稱` 是在vue-cli中安裝套件的其中一個方法

安裝好後，
* `main.js`會新增這行：`import store from './store'`
* 新增`store/index.js`檔案

### ▎State
儲存資料的物件
```javascript
state: {
  numbers: [1,3,5,7,9]
}
```

### ▎Mutations
包含函數的物件，負責接收actions並更改state資料。
mutations有幾個特點：
* 必定是同步函數
* 是vuex中唯一可以改動state的方法

要改變state需要有以下的流程：
1. dispatch an action｜發出一個action
2. commit a mutation｜接收到action後，執行對應的mutaion
3. 透過mutation更改state

這樣的做法可以確保不同的函數要操作同一個state資料的順序，會先在action中處理可能的非同步請求，當取得對應資料後再透過mutation以同步處理的方式變更state的資料。

每個mutation包含兩個參數：
1. `state`：可以自由取用或變動state的數值
2. `payload`：從actions傳來的參數
    > 若action不預期傳參數則可以省略不寫

```javascript
mutations: {
    ADD_NUMBER(state, payload) {
      state.numbers.push(payload)
    }
}
```

### ▎Actions
用來呼叫mutations，actions有幾個特性：
* 可以是非同步函數
* 一個action可以觸發多個mutations

actions包含兩個參數：
1. `context`：是一個物件，裡面可以使用store中的`commit`, `getter` 或 state屬性
2. `number`：想傳入mutation的參數，沒有可以省略

```javascript
actions: {
    addNumber(context,number) {
      context.commit("ADD_NUMBER", number)
  }
}

```
只需要使用某個屬性的話也可以寫成下面的方式：
```javascript
actions: {
    addNumber({commit},number) {
      commit("ADD_NUMBER", number)
    }
 }

```

### ▎getters
用來計算state (類似vue實體中的computed)

```javascript
getters: {
    sortedNumbers(state) {
      return state.numbers.sort((a, b) => a - b)
    },
}
```
> 使用`state`作為第一個參數

也可以寫成箭頭函數
```javascript
getters: {
    sortedNumbers: (state) => state.numbers.sort((a, b) => a - b)
 }
```
:::spoiler **複習箭頭函式的寫法**
基本寫法：
```javascript
(參數1, 參數2, …, 參數N) => { 陳述式; }

(參數1, 參數2, …, 參數N) => 表示式;
// 等相同(參數1, 參數2, …, 參數N) => { return 表示式; }
```

<br>

只有一個參數時,括號才能不加：
```javascript
(單一參數) => { 陳述式; }
單一參數 => { 陳述式; }
```
<br>

若無參數，就一定要加括號：
```javascript
() => { statements }
```
<br>

用大括號將內容括起來，返回一個物件字面值表示法：
```javascript
params => ({foo: bar})
```
<br>

支援其餘參數與預設參數：
```javascript
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }
```
<br>

也支援 parameter list 的解構：
```javascript
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c; f(); // 6
```
:::

### ▎在組件中使用`state`, `actions`, `getters`
```javascript
this.$store.state.numbers
this.$store.dispatch('某action')
this.$store.commit('某mutation')
this.$store.getters.sortedNumbers
```
### ▎`mapState`, `mapActions`, `mapGetters`
可以讓`state`, `actions`, `getters`更簡化

從vuex引入：
```javascript
import { mapState,mapActions,mapGetters } from 'vuex'

export default {
  data() {
  
  }
}
```
#### ▏用`mapState`, `mapGetters`取出`state`, `computed`屬性
```javascript
computed: {
    ...mapState(['numbers', 'user', 'isLoading']),
    ...mapGetters(['sortedNumbers']),
    自定義的computed屬性() {

    }
}
```
> **`...`**：**擴展運算子**，可以展開陣列，轉化成多個逗點相隔的獨立參數
> :::spoiler **複習擴展運算子**
>  * 寫法
>      ```javascript
>      ...arrayName
>      ```
>  * 範例
>      ```javascript
>      function foo(a, b, c) {
>         console.log(a + b + c);
>      }
>      let arr = [10, 20, 30];
>     
>      foo(...arr); // 60
>      // 等同於
>      foo.apply(null, arr); // 60
>      ```
>  * 特性
>      * 可以嵌在陣列裡：
>          ```javascript
>          let a1 = ['x', 'y'];
>          let a2 = ['w', ...a1, 'z'];
>          console.log(a2); // ['w', 'x', 'y', 'z']
>          ```
>      * 可以用來複製陣列：
>          ```javascript
>          let a1 = [1, 2];
>          let a2 = [...a1];
>          console.log(a2); // [1, 2]
>          ```
>      * 將字串展開為各單一字元：
>          ```javascript
>          let text = [...'Hello'];
>          console.log(text); // ['H', 'e', 'l', 'l', 'o']
>          ```
>  * [參考教學](https://ithelp.ithome.com.tw/articles/10195477)
> :::

#### ▏用`mapActions`取出`methods`屬性
```javascript
methods: {
    ...mapActions(['addNumber']),
    你的某些自定義methods屬性() {
      
    }
}
```
## ▊ Vue Router
前端模擬路由的套件，達成切換網址時也會切換元件。

因為vue框架是SPA (Single Page Application)，不希望每一次頁面變化時都向後端發送請求，只希望變化頁面，用模擬路由的方式就可以很好的達成。
![](https://hackmd.io/_uploads/Sk91DEuAh.png)


### ▎在vue-cli專案中加入router
在終端機輸入：`vue add router`，安裝router。

vue-cli會問是否使用history mode，選`y`代表採用HTML5的history API管理前端路由
> 可以透過 `pushState()` , `replaceState()` 的方式更新 URL， 以及原本就有的 `history.go()` , `history.back()` 來指定頁面的路徑切換，同時也提供了 state 物件來讓開發者暫存與讀取每一頁的狀態。

安裝後會新增：
* `view`, `router`兩個資料夾
* `App.vue`也有被修正

### ▎Vue Router 路由設定
`router/index.js`可以看到這段：
![](https://hackmd.io/_uploads/rksbTVO0h.png =400x)

<br>

**`createRouter()`** 有兩個部分：
* `history` 路由模式
* `routes` 路由比對

#### ▏`history` 路由模式
分為兩種：
* Hash mode
* HTML mode

Vue router預設是HTML mode

#### ▏`routes` 路由比對
`routes`是陣列型態，用來處理路徑和Vue實體元鑑比對的設定。
```javascript
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes: [
      { path: '/', component: Home},
      { path: '/about', component: About},
  ],
})
```
> * `path: '/', component: Home`：代表`path: '/'`對應到`Home.vue`，也就是在專案`http://localhost:8080/`的URL下，`<router-view>`顯示的是`Home.vue`的內容。
> * `path: '/about', component: About`：在專案`http://localhost:8080/about`的URL下，`<router-view>`顯示的是`About.vue`的內容。

<br>

* `path`：字串格式，用來表示對應的url，`'/'`表示根路由。
* `name`：除了`path`外，也可以用`name`來匹配對應的元件。
    * 若想配合`<router-link>`來匹配顯示的元件，必須以`v-bind`傳入物件的方式。
        ```javascript
        <router-link :to="{name: 'Home'}">Home</router-link>
        ```
    * `name`屬性具有唯一性，不能重名。
    * 雖然是用`name`去匹配，但實際上還是由`name`找到對應的`path`，所以url仍會顯示`/path`
* `component`：可以用兩種載入方式
    * 利用`import`將元件先行載入
        ```javascript
        import Home from '../views/Home.vue'
        ```
        ![](https://hackmd.io/_uploads/BkBydLuRh.png =450x)

    * 在`routes`內載入
        ```javascript
        component: () => import('../views/About.vue')
        ```

### ▎`router-link` & `router-view`
```javascript=
// App.vue
<template>
  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </nav>
  <router-view/>
</template>
```
在`App.vue`我們可以看到`<router-link>` 和 `<router-view>`

#### ▏`router-view`
`router-view`是要顯示的元件，當路由變化時，會去更新`router-view`這個元件，藉由路由或`name`屬性來控制要顯示哪個view。
```javascript
// 基本使用，自動預設name屬性為default
<router-view /> 
// 命名使用，此範例會強制渲染名為home的組件
<router-view  name="home"/> 
```

#### ▏`<router-link>`
用來切換連結的元件，實際上會渲染出一個`<a>`tag，做到hyperlink的效果。
![](https://hackmd.io/_uploads/r1wgzI_03.png =600x)

### ▎動態路由
router也提供了類似後端路由的功能，透過URL的動態路徑來讓不同路徑都能指向同一個Vue元件實體。

![](https://hackmd.io/_uploads/ryyxcIuA3.png =400x)

#### ▏Params
假設要有` localhost:8080/users/1`~` localhost:8080/users/5`5個url，可以寫成：
```javascript
routes: [{ path: '/users/:userId', component: User}]
```
* `:userId`可以自由命名，表示`/users/`後面的數值會傳入`userId`。
* 對應的元件是`User.vue`
* `User`元件可以透過`this.$route.params.userId`取得url的`userId`

<br>

`<router-link>`要生成5個連結，就可以寫成：
```javascript
<template>
  <ul>
    <li v-for="i in 5" :key="i">
        <router-link :to="`/users/${i}`"> /users/{{i}} </router-link>
    </li>
  </ul>
  <router-view/>
</template>
```

#### ▏query
`query.string`是指url問號後面的部分，可以利用`$route.query`來取得`query.string`。
例如：
* url為`localhost:8080/users?genger=male&age=25`
* `this.$route.query`會得到`{ gender:'male', age: '25'}`。

#### ▏`route`提供的屬性
* `$route.params`：存取自定義的變數
* `$route.query`：存取`location.search`
* `$route.hash`：存取`location.hash`
* `$route.path`：存取`location.pathname`

#### ▏`alias` 別名
以陣列的型態傳入，若是有匹配到任一別名，也能顯示對應的頁面。
```javascript
{ path: '/', name: 'Home', alias: ['home', 'homepage'], component: Home }
```
> `localhost:8080/`、`localhost:8080/home`、`localhost:8080/homepage` 都會顯示`Home`元件的內容。

#### ▏`redirect` 重新導向
```javascript
{
    path: '/', 
    name: 'Home', 
    alias: ['home', 'homepage'], 
    component: Home, 
    redirect: '/about'
}
```
可以用命名 (`name`) 、物件或函式的寫法：
```javascript
// url
redirect: '/about'
// 命名
redirect: About
// 物件
redirect: { About }
// 函式
redirect: from => return '/'
```

#### ▏自訂路由參數格式
可以在`path`內，透過**正規表達式 (regexp)** 指定`param`裡的格式。
```javascript
const routes = [
    {path: '/:orderId(\\d+)'}
]
```
> `/:orderId`只能是數字

#### ▏非強制的路由參數

若是`userId`這個參數可有可無，可以使用`?`：
```javascript
routes: [{ path: '/users/:userId?', component: User}]
```
>`localhost:8080/users`就能順利匹配到`User`元件

#### ▏路由比對失敗 (找不到網頁)
當使用者嘗試用不存在的URL進行讀取時，可以透過`/:pathMatch(.*)*`來指定所有的路由都會連到此元件。
```javascript
const routes = [
    { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound}
]
```
:::warning
含`*`的路由應該放在所有的路由最後面，避免原本正確的url還沒匹配就轉到`NotFound`。
:::

