Vue.js 筆記
===
###### tags: `web` `frontend` `JS Framework`
[官方網站](https://cn.vuejs.org/)
**CDN**
```
<script src="https://unpkg.com/vue"></script>
```
## 資料綁定
### 原生寫法
```htmlmixed=
<div id="content"></div>
<script>
var content = document.getElementById("content");
content.textContent = "Hello World";
</script>
```
### Vue
```htmlmixed=
<div id="app">
{{message}}
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: "Hello World"
}
});
</script>
```
:::info
el - 哪個範圍需要有 Vue 的功能(相當於指定範圍後才會有 Vue 的功能)
data - 資料綁定
:::
若是改變 data 中的變數值,也會連動改變頁面中相對應的值。
```
app.message = "changed"
```
#### v-once
如果不想要動態渲染,而是只希望渲染一次時,可以使用 ``v-once``,使用上請務必小心。
```htmlmixed=
<span v-once>這個將不會改變: {{ msg }}</span>
```
#### 還沒加載完出現大括號醜醜的?
可以使用 ``v-cloak``,並搭配 css 使用,這樣會在加載完成後才會顯示。
```htmlmixed=
<div v-cloak>
{{ message }}
</div>
```
```css=
[v-cloak] {
display: none;
}
```
#### 基本架構
```htmlmixed=
<div id='app'></div>
```
```javascript=
var app = new Vue({
el: '#app'
});
```
#### 觀念釐清
1.
```javascript=
var data = { a : 1 }
// 該對像被加入到一個 Vue 實例中
var vm = new Vue({
el: '#example',
data: data
})
// 他們引用相同的對象
vm.a === data.a // => true
// 設置屬性也會影響到原始數據
vm.a = 2
data.a // => 2
// ... 反之亦然
data.a = 3
vm.a // => 3
// 加上 $ 方便區隔 Vue 與一般 js 中的屬性名稱
vm.$data === data // => true
vm.$el === document.getElementById( 'example' ) // => true
```
:::info
Vue 會動態渲染 data 中的值,但其變動 data 必須是已存在於 data 中,而不是後來新增上去的。
像是 ``vm.b = 'hi'`` 中的 ``b`` 在 data 中沒有宣告,所以就算新增/改變也不會重新渲染。
解決方法可以在 data 中提前宣告,如果沒有初始值就給空值 ``''`` 也可以。
:::
#### Vue-Set
上方概念在實作中可能會遇到以下問題
```htmlmixed=
<div v-for="item in myArr">
{{ item.name }} - {{ item.age }}歲
</div>
<button @click="editName">點我修改陣列</button>
data: {
myArr: [
{
name: 'Bob',
age: 12
},
{
name: 'Marry',
age: 12
}
]
},
methods: {
editName: function(){
this.myArr[0].name = "Ken"
}
}
```
以上正確,但是如果對陣列做以下操作就會出現錯誤,也就是改完數值後畫面沒有重新渲染。
:::danger
```javascript=
editName: function(){
// 在 js 中,此做法可以清空陣列
// 在 Vue 中也確實會清空,但畫面不會更新
this.myArr.length = 0;
// 此處同上,資料有更新,但畫面不會更新
this.myArr[0] = {
name: 'Ken',
age: 20
}
}
```
:::
須改用 **``Vue.set``** 來解決這個問題,它的效果在於能夠把新的資料納入監控。
用法: ``Vue.set(data, index, newValue)``
:::success
```javascript=
editName: function(){
Vue.set(this.myArr, 0, {
name: 'Ken',
age: 20
})
}
```
:::
2. Vue 使用了基於 HTML 的模板語法,允許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。
Vue 將模板編譯成 Virtual DOM 渲染函數,在應用狀態改變時,Vue 能夠智能地計算出重新渲染組件的最小代價並應用到 DOM 操作上。
3. ``{{}}``,不單只是可以放變數,也可以加上表達式、函數
```htmlmixed=
{{ myFun() }}
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
```
但是不能這樣用
```htmlmixed=
<!-- 這是語句,不是表達式 -->
{{ var a = 1 }}
<!-- 流控制也不會生效,請使用三元表達式 -->
{{ if (ok) { return message } }}
```
## 指令
以下範例皆不再打出 Vue 作用範圍的 div 元素~
### v-model 雙向資料綁定
當 input 中的 value 改變時,data 中的 message 也會跟著改變,之後再重新渲染頁面上的所有 message。
```htmlmixed=
<input type="text" v-model="message">
{{message}}
```
也可以強制轉型接收到的值
```htmlmixed=
<input type="text" v-model.number="message">
```
### v-html
``{{data}}`` 只會將 data 的值當作普通文字來顯示,如果要加上 html 元素則要這樣寫
```htmlmixed=
<span v-html="rawHtml"></span>
```
### v-if
沒有出現的元素也不會在 DOM 節點中
```
data: {
loading: false,
score: null
}
```
```htmlmixed=
<div v-if="loading">沒有出現QQ</div>
<div v-if="1 > 0.5">我出現了</div>
```
```htmlmixed=
<h2>你考幾分呢?</h2>
<input type="number" v-model="score">
<div v-if="score >= 80">
厲害
</div>
<div v-else-if="score >= 70">
還不錯
</div>
<div v-else-if="score >= 60">
差點被當
</div>
<div v-else>
下學期再來吧
</div>
```
#### template
template 不會被輸出
如果 if 裡面想要同時包含多個元素,可以用 ``template`` 包覆。
```htmlmixed=
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
```
#### input 繼承
當 loginType 等於 username 時在 input 中輸入數值,但是當 loginType 不等於 username 切換到 Email 時,原本在 Username input 輸入的數值還是沿用,反之亦然。
```htmlmixed=
<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>
```
如果不想要繼承,只須加上 ``key``
```htmlmixed=
<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>
<!-- 也可以直接加在父元素上面,像是 v-for 生成時就很好用 -->
<div v-for="(item, index) in myArr" :key="index">
{{ item }}
<input type="text">
</div>
```
### v-show
show 和 if 作用差不多,差別在於 show 是透過 css 屬性讓元素顯示/消失,而 if 的消失是屬於徹底銷毀 DOM。
```htmlmixed=
<h1 v-show="ok">Hello!</h1>
```
:::warning
注意,v-show 不支持 <template> 元素,也不支持 ``v-else``
:::
### v-if vs v-show
:::info
``v-if`` 是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
``v-if`` 也是惰性的:如果在初始渲染時條件為假,則什麼也不做——直到條件第一次變為真時,才會開始渲染條件塊。
相比之下,``v-show`` 就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 進行切換。
一般來說,``v-if`` 有更高的切換開銷,而 ``v-show`` 有更高的初始渲染開銷。
因此,如果需要非常頻繁地切換,則使用 ``v-show`` 較好;如果在運行時條件很少改變,則使用 ``v-if`` 較好。
:::
### v-for
```
data:{
colors : ['blue','red','black'],
homes:[
{father:'tom'},
{father:'bob'},
{father:'john'}
]
}
```
```htmlmixed=
<h2>顏色列表</h2>
<ul>
<li v-for="color in colors">
{{color}}
</li>
</ul>
<h2>各家庭的父親</h2>
<ul>
<li v-for="home in homes">
{{home.father}}
</li>
</ul>
```
:::success
除了直接取值,也可以額外抓取 index(key)
:::
```htmlmixed=
<div v-for="(item, key) in students">
{{ key }} - {{ item.age }}
</div>
<!-- ryan - 18 ... -->
<div v-for="(item, key) in myArr">
{{ key }} - {{ item }}
</div>
<!-- 0 - red ... -->
data: {
students: {
ryan: {
age: 18
},
iris: {
age: 19
}
},
myArr: [red, orange, pink]
}
```
#### 還有可以直接產生數字
```htmlmixed=
<div v-for="item in 10">
{{ item }}
<!-- 1~10 -->
</div>
```
#### 結合 v-if
```htmlmixed=
<div v-for="item in student" v-if="item.age >= 18">
<div>
成年人:
</div>
{{ item.age }}
<!-- 1~10 -->
</div>
```
### v-on
:::info
1. v-on:click 可以縮寫為 @click
2. 所有 function 要寫在 methods 中
3. 可以加上修飾符(縮寫)來取消預設行為
ex:
@click.prevent="xxx"
@keyup.enter="xxx"
4. 除了接 function Name,也可以在裡面直接寫
ex: @click="isHungry = !isHungry"
(點了一下就餓,再點一下就不餓)
5. 常用的事件還有雙擊 :dblclick="xxx"
:::
```htmlmixed=
<ul>
<li v-for="home in homes" v-on:click="myFa(home.father)">
{{home.father}}
</li>
</ul>
```
```javascript=
var app = new Vue({
el:'#app',
data: {
homes:[
{father:'Tom'},
{father:'Bob'},
{father:'John'}
]
},
methods:{
myFa: function(name){
alert('我的爸爸是' + name)
}
}
})
```
### v-bind
#### 綁定 data 中的參數值到 html 元素屬性的設定值上
```htmlmixed=
<input type="button" v-bind:value="test">
<!-- 縮寫: -->
<input type="button" :value="test">
```
```javascript=
data: {
test: "我是按鈕"
},
```
#### 綁定 class 名稱
##### 物件
除了上面單純的變數值綁定寫法,也可以加上判斷。
這種物件寫法會判斷 ``:`` 後面的布林值是否成立而顯示前面的 class 名稱。
:::info
:class{ '要加入的 className' : boolean}
:::
```htmlmixed=
<div class="static"
v-bind:class="{ myClass: isActive, 'text-danger': hasError }">
</div>
<!-- 切換 -->
<button @click="isActive = !isActive"></button>
<button @click="hasError = !hasError"></button>
<input type="checkbox" v-model="isActive">
```
```javascript=
data: {
isActive: true,
hasError: false
}
```
當然也可以把 data 包成物件帶入
```htmlmixed=
<div v-bind:class="classObject"></div>
<!-- 切換 -->
<button @click="classObject.active = !classObject.active"></button>
<button @click="classObject.active = !classObject['text-danger']"></button>
<input type="checkbox" v-model="classObject.active">
```
```javascript=
data: {
classObject: {
active: true,
'text-danger': false
}
}
```
:::danger
記得有 ``-`` 的字串要用引號包起來
:::
##### 陣列
單純的變數值綁定寫法也可以透過陣列傳入多個值
```htmlmixed=
<div v-bind:class="[activeClass, errorClass]"></div>
<div v-bind:class="myArray"></div>
<!-- 切換 -->
<input type="checkbox" v-model="myArray" value="active">
<input type="checkbox" v-model="myArray" value="text-danger">
<div v-bind:class="['active', 'text-danger']"></div>
```
```javascript=
data: {
activeClass: 'active',
errorClass: 'text-danger',
myArray: ['active', 'text-danger']
}
```
##### 混用
當然也可以混用物件判斷的方法
```htmlmixed=
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
```
#### 計算屬性
進階一點可以加上複雜的邏輯判斷
```htmlmixed=
<div v-bind:class="classObject"></div>
```
```javascript=
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
```
#### 綁定 sytle
CSS 屬性名稱可以用駝峰也可以用一般橫槓寫法(記得用引號包起來)
```htmlmixed=
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
```
```javascript=
data: {
activeColor: 'red',
fontSize: 30
}
```
當然也可以直接傳入一個整合好的物件
```htmlmixed=
<div v-bind:style="styleObject"></div>
```
```javascript=
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
```
如果要傳入多個物件可以用陣列寫法
```htmlmixed=
<div v-bind:style="[styleObject, overridingStyles]"></div>
```
:::info
Class 與 Style 綁定寫法比較
1. Class 傳入的物件是判斷布林值來顯示名稱。
2. Style 如果要以傳入變數值的方式來寫,可以在 Style 後的 ``{}`` 中做字串拼接。
3. Class 可以單純傳入變數值,不一定要用物件。
ex: test: 'bg-success'。
4. 兩者都可以直接傳入包好的物件。
5. 兩者都可以使用陣列傳入多個物件或變數。
6. 兩者都可以使用計算屬性。
:::
## this
this 是 Javascript 的一個關鍵字。
在此範例中
```
function test(){
this.x = 1;
}
```
它代表函數運行時,自動生成的一個內部對象,隨著函數使用場合的不同, this 的值會發生變化。
但有一個總體的原則,那就是 this 指的是,調用函數的那個對象。
1. 純粹的函數調用
這是函數的最常見用法,屬於全局性調用,因此 this 就代表全局對象(Global)。
```
function test(){
this.x = 1;
alert(this.x);
}
test(); // 1
```
為了證明 this 就是全局對象,對程式碼做一些改變
```
var x = 1;
function test(){
alert(this.x);
}
test(); // 1
```
運行結果還是1。再變一下
```
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); // 0
```
2. 作為對象方法的調用
```
function test(){
alert(this.x);
}
var o = {};
o.x = 1;
o.m = test;
o.m(); // 1
```
3. 作為建構函數調用
```
function test(){
this.x = 1;
}
var o = new test();
alert(o.x); // 1
```
### 範例
```
data:{
price: 100
},
methods:{
sell: function(){
this.price -= 10;
}
}
```
```htmlmixed=
{{price}}
<input type="button" value="點擊扣十元" v-on:click="sell">
```
## 簡易ToDoList
```htmlmixed=
<div id="app">
<input type="text" placeholder="請輸入代辦事項" @keyup.enter="addTodo(newTodo)" v-model="newTodo">
<h2>事項列表</h2>
<ul>
<li v-for="todo in todos">
{{todo.content}}
- <a href="#" @click.prevent="removeTodo(todo)">刪除</a>
</li>
</ul>
</div>
```
```javascript=
var app = new Vue({
el: '#app',
data:{
todos:[],
newTodo:''
},
methods:{
addTodo: function(todo){
this.todos.push({content:todo,completed: false})
},
removeTodo: function(todo){
this.todos.splice(this.todos.indexOf(todo), 1);
}
}
});
```
## computed 計算屬性
收納 template 邏輯,即時更動資料就靠它。
和 methods 很像,但
- methods 如果 data 內容如果一樣就還是會全部重算;而 computed 則是有牽動到 data 才會跟著更換。
- 另外 computed 無法帶參數, methods 可以。
- 不確定每次都會更新,請用 computed;確定每次都會更新,就用 methods。
- 可以當參數使用
- 回傳邏輯計算後的結果
### 範例
1. 溫度換算
**methods**
```htmlmixed=
<h1>攝氏/華氏轉換計算機</h1>
<input type="text" placeholder="輸入攝氏溫度" v-model="celsius">
<br>
<span>換算過的華氏溫度:{{fahrenheit()}}</span>
```
```
data:{
celsius: 0
},
methods:{
fahrenheit : function(){
return this.celsius * 9/5 + 32;
}
}
```
**computed**
```htmlmixed=
<h1>攝氏/華氏轉換計算機</h1>
<input type="text" placeholder="輸入攝氏溫度" v-model="celsius">
<br>
<span>換算過的華氏溫度:{{fahrenheit}}</span>
```
```
data:{
celsius: 0
},
computed:{
fahrenheit : function(){
return this.celsius * 9/5 + 32;
}
}
```
2. 變更資料才觸發 computed
時間應該只會在第一次載入時刷新,但是在 computed 函數中加上 a、b,會在 a、b 值變動而連動執行函數。
```htmlmixed=
現在時間 : {{now}}
<br>
路人A體脂肪:<input type="text" v-model="a">
<br>
路人B體脂肪:<input type="text" v-model="b">
```
```
data:{
a: 0,
b: 0
},
computed:{
now: function (){
let total = this.a + this.b;
var d = new Date();
return d.getSeconds();
}
}
```
## watch
監聽 data 變化而執行函數,與 computed 相似,但是用在某些情況下還是會有 computed 無法處理的情況。
[看文件](https://cn.vuejs.org/v2/guide/computed.html#侦听器)
```htmlmixed=
<input type="text" v-model="text">
```
```javascript=
data: {
text: ''
},
watch: {
text: 'myFun',
// 其他寫法
text: function(){
this.myFun();
},
// 其他寫法
text: function(val){
alert(val);
}
},
methods: {
myFun: function(){
alert(this.text);
}
}
```
## 常用修飾符
### 事件
```
// 阻止原生行為
@event.prevent
// 只觸發一次
@event.once
// 監聽按鍵
@keyup.enter
@keyup.esc
@keyup.space
// 監聽滑鼠
@click.left
@click.right
@click.middle
// 阻止冒泡事件
@event.stop
ex:
.ul @click="alert('Click ul')"
.li @click="alert('Click li')"
這樣點 li 會先出現 "click li" 但是隨後也會出現 "click ul"
改成這樣點選 li 就只會觸發 li
.ul @click.stop="alert('Click ul')"
.li @click.stop="alert('Click li')"
// 監聽模式改為 capture
@event.capture
同上,但是反過來,點 li 會先出現 "click ul" 再出現 "click li"
// 監聽模式改為 self
@event.self
只會觸發被點選的元素
```
### v-model
```
// 強制轉型變數為數字
v-model.number
// 去掉首尾空白
v-model.trim
// 類似於 onchange,只有在離開焦點後才會觸發
v-model.lazy
```
## 表單綁定
### text、textarea
``text``、``textarea``都可以用 v-model 直接雙向綁定文字內容。
```htmlmixed=
<input type="text" v-model="myText">
<textarea v-model="myFeedBack"></textarea>
```
### checkbox
一樣使用 v-modedl,若是存放的變數非陣列,取到的值為 boolean (是否勾選),若是陣列,則會取到 value 中的值。
```htmlmixed=
<input type="checkbox" v-model="bool" value="Tom">
{{ bool }} // true or false
<input type="checkbox" v-model="arr" value="Tom">
{{ arr }} // [Tom]
data: {
bool: '',
arr: []
}
```
也可以一次取很多 value
```htmlmixed=
<input type="checkbox" v-model="arr" value="Tom">
<input type="checkbox" v-model="arr" value="Marry">
<input type="checkbox" v-model="arr" value="Bob">
{{ arr }} // [Tom, Marry, Bob]
data: {
arr: []
}
```
上面提到 ``v-model`` 綁定的值,如果是陣列才能夠取到 input 的 value,但是有一個方法可以不用陣列取得 input 的值,也很實用。
```htmlmixed=
<input type="checkbox" v-model="sex" value="man">
{{ sex }} // ture or false
<!-- 加上 true、false value -->
<input type="checkbox" v-model="sex" true-value="man" false-value="woman">
{{ sex }} // man or woman
data: {
sex: '',
}
```
### radio
radio 為單選,v-model 將會取出選擇的 value
```htmlmixed=
<input type="radio" v-model="single" value="Tom">
<input type="radio" v-model="single" value="Marry">
<input type="radio" v-model="single" value="Bob">
{{ single }} // Tom or Marry or Bob
data: {
single: ''
}
```
### select
select 一樣為單選,v-model 將會取出所選 option 的 value
```htmlmixed=
<select v-model="option">
<option value="" disabled="">-- 請選擇 --</option>
<option value="red">紅色</option>
<option value="blue">藍色</option>
<option value="green">綠色</option>
</select>
{{ option }} // red or blue or green
data: {
option: ''
}
```
## 元件 Component
### 基礎觀念
```htmlembedded=
<button @click="counter += 1">{{ counter }}</button>
data: {
counter: 0
}
```
以上程式碼為簡單的點擊計數器,但卻無法重複使用,若是多複製幾行,會發現它們三個使用同一個 counter,也就是不管點選哪一個,其他兩個的值也會增加。
```htmlembedded=
<button @click="counter += 1">{{ counter }}</button>
<button @click="counter += 1">{{ counter }}</button>
<button @click="counter += 1">{{ counter }}</button>
```
此時可以採用元件的寫法
:::warning
data 一樣為使用的變數,但不能直接寫成物件,而是要用 function 回傳。
:::
```htmlmixed=
<counter-component></counter-component>
<counter-component></counter-component>
<counter-component></counter-component>
Vue.component('counter-component', {
data: function (){
return {
counter: 0
}
},
template: `
<div>
<button @click="counter += 1">{{ counter }}</button>
</div>
`
});
```
#### x-template
上方的元件內容(元素)是直接在 ``template`` 中定義,但也可以改成下方寫法:
```htmlmixed=
<say-hi></say-hi>
<!-- 此處的 id 對應到 template 指定的 id, -->
<!-- 不一定要和元件標籤名稱一樣 -->
<script type="text/x-template" id="my-component">
<div>Hi</div>
</script>
<script>
Vue.component('say-hi', {
template: '#my-component'
});
</script>
```
### is
HTML 對於標籤的位置有規定,像是下方雖然可以渲染,但是內容會跑到 form 外面,這是因為 form 中不允許 my-test 存在。
```htmlmixed=
<form>
<my-test></my-test>
</form>
<script type="text/x-template" id="my-test">
<tr>
<td>123</td>
</tr>
</script>
```
改成這樣就可以在原位置正常渲染了
```htmlmixed=
<form>
<tr is="my-test"></tr>
</form>
```
#### 使用 is 動態切換元件
如果有多個元件的顯示是根據某個變數的狀態,大概長這樣
```htmlmixed=
<primary-component v-if="current === 'primary-component'"></primary-component>
<danger-component v-if="current === 'danger-component'"></danger-component>
<a href="#" @click.prevent="current = 'primary-component'">綠色元件</a>
<a href="#" @click.prevent="current = 'danger-component'">紅色元件</a>
<script>
var app = new Vue({
current: 'primary-component'
}
});
</script>
```
程式碼少的話還可以,但是結構複雜時,v-if 就沒那麼好用,而且程式碼會很髒,所以可以把 ``is`` 結合動態綁定變數的方式,改成下方寫法:
```htmlmixed=
<div :is="current"></div>
<a href="#" @click.prevent="current = 'primary-component'">綠色元件</a>
<a href="#" @click.prevent="current = 'danger-component'">紅色元件</a>
```
### 局部註冊
上面用的註冊元件寫法都是下方這樣,但這樣代表所有的 vue 應用程式(app、app2...)都可以使用這個元件,
```htmlmixed=
<script>
Vue.component('say-hi', {
template: '#my-component'
});
</script>
```
想要局部註冊某個元件可以用這種寫法:
```htmlmixed=
<say-hi></say-hi>
<script type="text/x-template" id="row-component">
<div>Hi</div>
</script>
var child = {
template: '#row-component'
}
var app = new Vue({
components: {
'say-hi': child
}
});
```
### props
元件沒辦法直接存取外部變數,簡單來說,元件能夠使用的變數除了事先定義好的之外,就只能透過 ``props`` 把外部的變數傳入。
傳入值一樣可以選擇綁定變數(加上:),或是直接給值。
```htmlmixed=
<!-- 將外部變數 message 傳入元件中的 msg 變數 -->
<!-- 若是駝峰寫法在 html 需改成 '-' -->
<say-hi :msg="message" test-inside="test"></say-hi>
<script type="text/x-template" id="my-component">
<div>{{ msg }}</div>
<div>{{ testInside }}</div>
</script>
<script>
Vue.component('say-hi', {
template: '#my-component',
// 必須先定義好接收外部變數的內部變數
props: ['msg','testInside']
});
var app = new Vue({
data: {
message: "Hi"
}
});
</script>
```
#### 單向數據流
不要在元件裡面去改變 props 傳入的變數,而是新增一個變數來取代傳入的外部變數。
```htmlmixed=
<photo :img-url="url"></photo>
<script type="text/x-template" id="photo">
<div>
<img :src="imgUrl" class="img-fluid" alt="" />
// 錯誤的,不應該修改傳入的 imgUrl
<input type="text" class="form-control" v-model="imgUrl">
// 正確的
<input type="text" class="form-control" v-model="newUrl">
</div>
</script>
Vue.component('photo', {
props: ['imgUrl'],
template: '#photo',
data: function () {
return {
newUrl: this.imgUrl
}
}
})
```
#### ajax 等待問題
如果要渲染或是傳入元件的資料是透過 ajax 撈取,可能會發生渲染時資料還沒撈回來的狀況,可以使用 ``v-if`` 解決這個問題。
```htmlmixed=
<!-- v-if 可以選個一定會有的資料內容 -->
<card :user-data="user" v-if="user.id"></card>
<script>
var app = new Vue({
el: '#app',
data: {
user: {},
isShow: true
},
created: function () {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function (data) {
vm.user = data.results[0];
}
});
}
});
</script>
```
#### 限制型別與預設值
透過 props 傳入的變數能夠事先定義預設值,以及接收的資料型態,若是不符則會報錯。
```javascript=
Vue.component("test", {
props: {
cash: {
type: Number,
default: 100
}
}
});
```
### emit
為了避免開發上的混亂,因此在使用元件時,會是希望資料是單向流動的,而不是內外共用,所以當元件想要取得外部的變數值時,可以使用 ``props``,但是如果元件想要直接改變外部的變數呢?
此時就要使用 ``emit``,透過自訂義的事件來處發後續動作。
以下範例運作流程為:
在元件外部綁定名為 add 的事情,當觸發時執行外部的 addMoney 函數,
而元件內部則有一個綁定 click 事件的 button、和綁定內部變數 counter 的 input,
當按鈕被點擊時會觸發內部 incrementCounter 函數,其作用為透過 $emit 觸發指定事件(可帶入參數),而元件外部接收到該指定事件後,接著觸發 addMoney。
```htmlmixed=
<!-- addMoney 為外部函數 -->
<!-- add 為自訂義事件 -->
<!-- 也就是當名為 add 的事件觸發時,將會執行 addmoney() -->
<button-counter @add="addMoney"></button-counter>
<!-- 外部變數 cash -->
{{ cash }}
<script>
Vue.component('buttonCounter', {
template: `<div>
<button @click="incrementCounter" class="btn btn-outline-primary">增加 {{ counter }} 元</button>
<input type="number" class="form-control mt-2" v-model="counter">
</div>`,
data: function() {
return {
counter: 1
}
},
methods: {
// 使用 emit 觸發指定事件,此處為觸發 add 事件
// 後面可以帶入參數
incrementCounter: function(){
this.$emit('add', Number(this.counter))
}
}
});
var app = new Vue({
data: {
cash: 300
},
methods: {
addMoney: function(addNum){
this.cash += addNum;
}
}
});
</script>
```
### slot
在使用元件時,可能會需要改變裡面的內容,不可能每個元件都剛好長的一樣。
#### 沒有定義插槽時,在 html 標籤中的內容會直接被元件定義好的模板覆蓋。
```htmlmixed=
<!-- 渲染時 123 會被 p 段落直接蓋過 -->
<no-slot-component>
123
</no-slot-component>
<script type="text/x-template">
<p>
這沒有插槽。
</p>
</script>
```
#### 匿名插槽
元件標籤中所有的元素都會被放到 ``slot`` 插槽裡面
```htmlmixed=
<!-- 渲染時會 h6 下方會變成 p 段落 -->
<single-slot-component>
<p>我是自訂義的新內容喔</p>
</single-slot-component>
<!-- h6 下方會顯示: 我是預設內容... -->
<single-slot-component></single-slot-component>
<script type="text/x-template" id="singleSlotComponent">
<h6>我是一個元件</h6>
<slot>
我是預設內容,如果沒有外部內容,則會顯示此段落。
</slot>
</script>
```
#### 具名插槽
匿名插槽解決了可以自訂新內容的問題,但是它會將標籤中的所有元素都放到插槽中,如果是遇到較複雜的結構,像是要自訂義 header、content、footer 內容時,就沒辦法透過單個 ``slot`` 解決,遇到這個狀況可以使用具名插槽,也就是替 ``slot`` 加個 name。
##### 用法:
```htmlmixed=
<mt-test>
<header slot="new-header">New header</header>
<footer slot="new-footer">New footer</footer>
</mt-test>
<script type="text/x-template">
<slot name="new-header">預設 header</slot>
<slot name="new-footer">預設 footer</slot>
</script>
```
##### 範例:
```htmlmixed=
<named-slot-component>
<header slot="header">替換的 Header 666</header>
<template slot="footer">替換的 Footer</template>
<template slot="btn">按鈕內容</template>
<p slot="other">其餘的內容</p>
</named-slot-component>
<script type="text/x-template" id="namedSlotComponent">
<div class="card my-3">
<div class="card-header">
<slot name="header">這是預設的 header 文字</slot>
</div>
<div class="card-body">
// 若外部有傳入 other,則此處的 h5、p 都會被覆蓋
<slot name="other">
<h5 class="card-title">我是預設內容</h5>
<p class="card-text">我也是預設內容</p>
</slot>
<a href="#" class="btn btn-primary">
<slot name="btn">預設按鈕文字</slot>
</a>
</div>
<div class="card-footer">
<slot name="footer">這是預設的 Footer 文字</slot>
</div>
</div>
</script>
```