[TOC]
## 1-1 Vue.js 簡介
### 漸進式

紅色部分是最核心的特色,可以依照專案需求漸進式套用其他需要的東西
### 指令式渲染 vs. 宣告式渲染
原生JS以「操作 DOM 為基礎」的模式為指令式渲染
取得元素,操控元素,一個指令一個動作
很多元素的時候會很亂,無法管理

Vue.js則是將資料/狀態統一由 JS 的**物件**來維護管理,事件改變物件內的狀態,狀態被修改後再去更新模板的內容,也就是MVVM模式
```html
<div id="demo">
<h1>{{ message }}</h1>
<input v-model="message">
</div>
```
```javascript
// 建立一個Vue的物件實體,並且將這個物件指定至變數vm之中。
const vm = Vue.createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#demo'); // 宣告後馬上掛載到指定的網頁節點
// 也可以之後再掛載
vm.mount('#demo');
```
這邊的`{data(){...}}`是稱為`options`的物件參數
透過`mount`,表示`vm`這個 Vue.js 實體物件 (根元件) 可控制的範圍為`#demo`這個節點
### MVVM 模式

將 DOM 的事件監聽與狀態的資料綁定封裝起來 (ViewModel, VM)
使用者操作 View (V)
透過 VM 內的 Vue.js 把狀態回存至 Model (M) (某個物件,以上面的程式碼來說就是 `vm` ?)
Model 被更新後 VM 內的 Vue.js 也會同步更新網頁模板 (V)
### DOMContentLoaded
如果程式碼寫在`<head>`裡面會因為瀏覽器還沒解析到`<body>`內的DOM而出錯,可以利用`DOMContentLoaded`事件等到DOM完全載入後再執行
```javascript
const vm = Vue.createApp({
// 略
});
document.addEventListener("DOMContentLoaded", () => {
// DOM Ready!
vm.mount('#app');
});
```
## 1-2 Vue.js 的核心: 實體
### 把實體掛載至 DOM
方法一:在`options`設定`el`屬性 (3.0不能用)
```javascript
// for Vue 2.x
const vm = new Vue({
el: '#app'
});
```
方法二:用`$mount` (2.x) 或 `mount` (3.0) 方法
```javascript
// 新增節點然後加入至 body
const el = document.createElement('div');
document.body.appendChild(el);
// Vue 2.x
vm.$mount(el);
// Vue 3.0
vm.mount('#app');
```
`el`屬性和`mount()`設定的節點可以是 CSS 選擇器,也可以是 DOM 物件
:heavy_check_mark:可以用`vm.mount(el);`嗎
可以!
#### 測試結果
:::spoiler
```javascript
import {
createApp,
ref,
} from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
const el = document.createElement("div");
el.setAttribute("id", "child");
el.textContent = "{{ name }}已陣亡";
document.body.appendChild(el);
const vm = createApp({
data() {
return {
name: "天兔",
};
},
mounted() {
console.log("呀哈");
},
});
vm.mount(el);
```
>
:::
#### 測試失敗紀錄
:::spoiler
在實作的時候一直失敗,看到官方文件這句話
>
而檢查檔案中有使用到.mount()的地方只有main.js

所以猜測,在App.vue裡面集合了所有要拿來製作實體的東西(?),然後一次在App.vue中用createApp()創造這個實體,並且掛載上去
:::
### 定義狀態
在 Vue 實體的定義的狀態,就是透過 `data` 屬性來儲存。
`data` 屬性是function的形式
```javascript
const vm = Vue.createApp({
// 實體所回傳的狀態會以物件 key-value 的形式
// 且在 Vue 3.0 開始,data 將強制以 function 的形式出現
data () {
return {
name: '008JS'
}
}
});
```
### 模板語法
採用的是 Mustache 語法
> [從 Mustache 的 render 函式了解模板系統如何解析並渲染傳入的資料](https://uu9924079.medium.com/從-mustache-的-render-函式了解模板系統如何解析並渲染傳入的資料-e32f4ca4dd50)
利用`{{ }}`把定義的狀態輸出到html上
```html
<div id="app">
{{ name }} 好棒棒!
</div>
```

理解:也就是在data內存放變數,可以利用這個變數直接在html上渲染
Vue.js 會自動將 data 內的屬性加上 getter 與 setter 的特性,以便監控狀態的更新 (響應式更新)。
在 Vue.js 的實體當中,以底線 _ 或錢字號 $ 作為開頭的屬性,不會被加上 getter 與 setter 的特性,命名時避免使用_和$作開頭
定義的狀態要怎麼更新?透過 `vm.$data.XXX` 來操作內部狀態。(注意 vm.mount('#app') 之後才會產生對應的 $data)
```javascript
const vm = Vue.createApp({
// 實體所回傳的狀態會以物件 key-value 的形式
// 且在 Vue 3.0 開始,data 將強制以 function 的形式出現
data () {
return {
name: '008JS'
}
}
}).mount('#app'); // 建立實體的同時要掛載到DOM上!!!
vm.$data.name = "30cm"; // 才能更改
```
如果建立實體的同時沒有掛載到DOM上,就無法取得$data
```javascript
const vm = createApp({
data() {
return {
name: "天兔",
};
},
mounted() {
console.log("呀哈");
},
});
vm.mount(el);
vm.$data.name = "小八";
```
>
```javascript
const vm = createApp({
data() {
return {
name: "天兔",
};
},
mounted() {
console.log("呀哈");
},
}).mount(el);
vm.$data.name = "小八";
```
>
在`{{ }}`內也可以進行變數之間的運算,最後將算出來的值輸出到畫面上
`{{ }}`可以自定義更改,例如改成`%{ }%`,設定`delimiters`屬性。但這個屬性只支援在「瀏覽器」即時編譯的版本中使用。
```javascript
const vm = Vue.createApp({
delimiters: [`%{`,`}%`],
data () {
return {
name: '008JS'
}
}
});
```
### 共用 data 的汙染
定義在Vue實體外的data,可以讓不同的Vue實體共用這個data的設定,但更改實體A的data也會同時改變實體B的狀況,建議使用淺拷貝或深拷貝`JSON.parse(JSON.stringify(...))`的方式引入實體A和實體B
### template 模板屬性
除了直接用`{{ }}`寫在html,也可以使用`template`屬性
```javascript
const vm = Vue.createApp({
template: `<div>{{ greeting }} 好棒棒!</div>`,
data () {
return {
greeting: 'Hello Vue.js!'
}
}
}).mount('#app');
```
## 1-3 資料加工與邏輯整合
### methods 方法
重複的程式片段包成function,function可以放在`methods`屬性,並作為一個物件的屬性名稱。
```javascript
const vm = Vue.createApp({
data () {
return {
price: 100,
quantity: 10,
}
},
methods: {
subtotal: function () { // 小心不要寫成箭頭函式
return this.price * this.quantity; // 記得要加this
}
// 但ES6的方法可以簡寫成這樣
sum () {
return this.price * this.quantity;
}
}
}).mount('#app');
```
> [ES6的方法簡寫](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions)
在模板中呼叫這個方法就可以使用了
```html
<div id="app">
總額是{{ subtotal() }}元
</div>
```
### computed
和methods很像,使用上不用小括號執行
```javascript
const vm = Vue.createApp({
data () {
return {
price: 100,
quantity: 10,
}
},
computed: {
subtotal: function () {
return this.price * this.quantity;
}
}
}).mount('#app');
```
```html
<div id="app">
<!-- 不用小括號 -->
總額是{{ subtotal }}元
</div>
```
和methods的差別在於,computed只會計算一次並把結果暫存起來,直到它裡面的屬性(price, quantity之類的)被更新,才會再次執行
所以執行效率上computed比較快,但缺點是computed中的function無法帶入參數(因為不用小括號)
:heavy_check_mark:當我們此時嘗試要去更新 data 裡的 message 屬性時,會觸發 methods 的呼叫,而 computed 卻不會再次執行。
#### 測試結果
:::spoiler
原本書上html是寫下面這樣
```html
<div id="test1">
<p>總金額共{{ subtotalComputed }}元</p>
<p>總金額共{{ subtotalMethods() }}元</p>
</div>
```
搭配以下的js,在最後更新 data 裡的 message 屬性
```javascript
const vm = createApp({
data() {
return {
message: "Hello Vue!",
};
},
computed: {
subtotalComputed: function () {
console.log("computed");
return 100 * 100;
},
},
methods: {
subtotalMethods() {
console.log("methods");
return 100 * 100;
},
},
}).mount("#test1");
vm.$data.message = "Hey";
```
但是都只有呼叫一次而已

所以我把html加上一行要取用message的地方
```html
<div id="test1">
<p>總金額共{{ subtotalComputed }}元</p>
<p>總金額共{{ subtotalMethods() }}元</p>
{{ message }}
</div>
```
就可以看到methods被執行了兩次

:::
### computed / methods 的使用時機比較
幣值轉換
```html
<!-- 使用methods -->
<!-- 要綁v-on,雖然現在還看不懂v-on是啥 -->
<div id="app">
<p>1 日幣 = 0.278 台幣</p>
<div>台幣 <input type="text" v-model="twd" v-on:input="twd2jpy"></div>
<div>日幣 <input type="text" v-model="jpy" v-on:input="jpy2twd"></div>
</div>
<!-- 使用computed -->
<div id="app">
<p>1 日幣 = 0.278 台幣</p>
<div>台幣 <input type="text" v-model="twd"></div>
<div>日幣 <input type="text" v-model="jpy"></div>
</div>
```
>之後會提到 v-model 是幹嘛的
>各位讀者只要知道這裡放上 v-model ,使用者輸入的內容會跑進 Vue 實體對應的屬性即可。
```javascript
// 使用methods,每一種轉換就要寫一個function
const vm = Vue.createApp({
data () { // 每一個幣值就要記一個
return {
twd: 0.278,
jpy: 1,
}
},
methods: { // 要寫兩個function
twd2jpy () {
this.jpy = Number.parseFloat(Number(this.twd) / 0.278).toFixed(3);
},
jpy2twd () {
this.twd = Number.parseFloat(Number(this.jpy) * 0.278).toFixed(3);
},
},
mounted () { // 這個component掛載的時候會執行的function
this.twd2jpy();
}
}).mount('#app');
// 使用computed,重點是從台幣轉換成各種幣值,也就是以台幣為基準點來轉換
const vm = Vue.createApp({
data () { // 紀錄台幣就好
return {
twd: 0.278 // 這個型別是string
}
},
computed: {
jpy: {
get () {
return Number.parseFloat(Number(this.twd) / 0.278).toFixed(3);
},
set (val) {
this.twd = Number.parseFloat(Number(val) * 0.278).toFixed(3);
}
}
}
}).mount('#app');
```
#### 自行測試
把各種幣別統一管理在methods裡面,再用`this.XXX`取用這個method
```javascript
methods: {
getExchangeRate(currency) {
let rate = 1;
switch (currency) {
case "jpy":
rate = 0.22;
break;
case "usd":
rate = 31.5;
break;
}
return rate;
},
},
computed: {
jpy: {
get() {
return Number(Number(this.twd) / this.getExchangeRate("jpy")).toFixed(2);
},
set(value) {
this.twd = Number(Number(value) * this.getExchangeRate("jpy")).toFixed(2);
},
},
usd: {
get() {
return Number(Number(this.twd) / this.getExchangeRate("usd")).toFixed(2);
},
set(value) {
this.twd = Number(Number(value) * this.getExchangeRate("usd")).toFixed(2);
},
},
},
```
:question:在輸入時會一直強制變成小數點兩位的狀態
## 1-4 Vue.js 的黑魔法: 指令
### 屬性綁定 - `v-bind`
屬性也用變數的方式去渲染
```javascript
const vm = createApp({
data() {
return {
isDisabled: true,
};
},
}).mount(#app);
```
```html
<button v-bind:disabled="isDisabled">click me!</button>
```
渲染出來就是`disabled`這個屬性是`isDisabled`屬性的值`true`

### 表單綁定 - `v-model`
要是表單元素才可以透過`v-model`進行雙向綁定,例如 `<input>`、`<textarea>` 以及 `<select>`
雙向綁定理解:輸入的值會設定成`v-model`的變數的值。
#### `<input type="radio">`
原本同一組radio要使用同一個`name`才能綁在一起
```html
<div>
<input
type="radio"
name="contact"
id="contactChoice1"
value="email"
/>
<label for="contactChoice1">Email</label>
<input
type="radio"
name="contact"
id="contactChoice2"
value="phone"
/>
<label for="contactChoice2">Phone</label>
<input
type="radio"
name="contact"
id="contactChoice3"
value="line"
/>
<label for="contactChoice3">Line</label>
</div>
```
現在可以設定同一個`v-model`,讓radio綁成一組
```html
<div>
<input
type="radio"
id="contactChoice1"
value="email"
v-model="contact"
/>
<label for="contactChoice1">Email</label>
<input
type="radio"
id="contactChoice2"
value="phone"
v-model="contact"
/>
<label for="contactChoice2">Phone</label>
<input
type="radio"
id="contactChoice3"
value="line"
v-model="contact"
/>
<label for="contactChoice3">Line</label>
</div>
```
並且在實體中設定,就會預設選擇設定的那個value
```javascript
const vm = createApp({
data() {
return {
contact: "line",
};
},
}).mount("#app");
```

#### 自己亂測試
設定成同一個`v-model`之後,即使`name`不一樣也會綁在一起
設定成同一個`name`,即使`v-model`不一樣也會綁在一起
#### `<input type="checkbox">`
和radio的差別是實體中設定要是陣列,如果不是陣列就會全選
#### `<input type="select">`
他的範例壞掉惹,但程式碼是對的
把「請選擇」的value=""刪掉好像也沒關係
```html
<select v-model="selected">
<option disabled>請選擇</option>
<option>台北市</option>
<option>新北市</option>
<option>基隆市</option>
</select>
<p>Selected: {{ selected }}</p>
```
讓一開始選項停在「請選擇」
```javascript
const vm = createApp({
data() {
return {
selected: "請選擇",
};
},
}).mount("#app");
```
### v-model 與修飾子
#### `.lazy`
從監聽input事件改為change事件,當使用者離開輸入框焦點才會觸發事件
#### `.number`
input輸入的型別從字串改為數字,如果想取得的是數字就可以自動幫忙轉型並作運算
#### `.trim`
自動刪除輸入前後的空白
### 模板綁定 - v-text / v-html / v-once / v-pre
#### `v-text`
跟{{ }}有類似的效果,但設定在標籤上的話就會把這個標籤的文字(我猜是text-content)(書上寫innerHTML)換成變數的內容
#### `v-html`
把標籤的innerHTML換成變數的內容
#### `v-once`
只會渲染一次
#### `v-pre`
不使用指令
如果想要顯示{{ }}大括號本身的話就要用這個指令
`<div v-html="rawContent" v-pre></div>` 這樣也不會顯示rawContent的值
### v-bind 樣式綁定 class/style
```css
.error {
color: red;
}
```
綁class`error`,語法會判斷屬性(error)的值(isError)的truthiness,是true就加上這個屬性成為class
[官方文件](https://vuejs.org/guide/essentials/class-and-style.html)
```html
<input type="text" v-model.trim="title" v-bind:class="{error: isError}" />
<input
type="text"
v-model.trim="title"
v-bind:class="{ error: title.length > 10 }"
/>
```
把邏輯移到computed
```javascript
computed: {
isError: function () {
return this.title.length > 10;
},
},
```
style中的css推薦使用駝峰式,如果用烤肉串式要用引號括起來
```html
<input
type="text"
v-model.trim="title"
v-bind:style="{ fontSize: fontSize }"
/>
```
```javascript
data() {
return {
fontSize: "30px",
};
},
```
放進array,例如跟瀏覽器支援相關的,會使用最後一個瀏覽器有支援的
```html
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
```