這篇文章你將看到以下內容:
- 一個iOS開發視角學習Vue需要學習的一些知識點
Vue實例結構
var vm = new Vue({
//樣式表名
el: '#example',
//屬性
data: {
message: 'Hello'
},
//計算屬性
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
//屬性觀察
watch: {
// 如果 `question` 發生改變,這個函數就會運行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
//實例函數
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
})
Vue監聽實例
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- 因為 AJAX 庫和通用工具的生態已經相當豐富,Vue 核心代碼沒有重復 -->
<!-- 提供這些功能以保持精簡。這也可以讓你自由選擇自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// 如果 `question` 發生改變,這個函數就會運行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.getAnswer()
}
},
methods: {
// `_.debounce` 是一個通過 Lodash 限制操作頻率的函數。
// 在這個例子中,我們希望限制訪問 yesno.wtf/api 的頻率
// AJAX 請求直到用戶輸入完畢才會發出。想要了解更多關于
// `_.debounce` 函數 (及其近親 `_.throttle`) 的知識,
// 請參考:https://lodash.com/docs#debounce
getAnswer: _.debounce(
function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
},
// 這是我們為判定用戶停止輸入等待的毫秒數
500
)
}
})
</script>
Vue實例生命周期
beforeCreate->created->beforeMount->mounted->beforeUpdate->updated->beforeDestroy->destryed
v-bind
綁定標簽字段與Vue對應關系
<!-- 完整語法 -->
<a v-bind:href="url">...</a>
<!-- 縮寫 -->
<a :href="url">...</a>
v-on
綁定事件與Vue對應關系
<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>
<!-- 縮寫 -->
<a @click="doSomething">...</a>
Vue組件
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
Class綁定
普通
<div v-bind:class="{ active: isActive }"></div>
數組
HTML:
<div v-bind:class="[activeClass, errorClass]"></div>
JS:
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
條件
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
Style綁定
HTML:
<div v-bind:style="styleObject"></div>
JS:
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
v-if
<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>
關于是否顯示,v-show同樣可以達到相同效果,不同的是
v-show 的元素始終會被渲染并保留在 DOM 中。v-show 只是簡單地切換元素的 CSS 屬性 display。
注意,v-show 不支持 <template> 元素,也不支持 v-else。
v-if 與v-show
v-if 是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
v-if 也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
相比之下,v-show 就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進行切換。
一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好。
v-for
HTML:
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
JS:
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
對于對象同樣可以使用v-for語法
HTML:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
JS:
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
}
})
當v-for配合v-if同時使用時,相當于在for循環中添加if判斷。
2.2.0+ 的版本里,當在組件中使用 v-for 時,key 現在是必須的。
eg.:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
TODO LIST的完成例子
HTML:
<div id="todo-list-example">
<input
v-model="newTodoText"
v-on:keyup.enter="addNewTodo"
placeholder="Add a todo"
>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
JS:
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
關于數組
Vue為數組提供了很多變異方法,一邊觀察數組的變化從而更新試圖:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
同時,Vue也提供了一些非變異方法,他們不會改變原數組的值而是返回一個新的數組:
- filter()
- concat()
- slice()
在使用上,我們可以用一個新數組替換舊數組,Vue內部做了優化,提高渲染效率。
值得注意的是,由于 JavaScript 的限制,Vue 不能檢測以下變動的數組:
當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
當你修改數組的長度時,例如:vm.items.length = newLength
應對第一種問題我們可以使用以下三種方式進行值得替換:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
// Vue.set的全局縮寫
vm.$set(vm.items, indexOfItem, newValue)
第二種問題解決方案:
vm.items.splice(newLength)
不僅是數組,對象同樣會有Vue檢測不到的變化:
Vue 不能檢測對象屬性的添加或刪除
為了解決這個問題我呢們可以調用以下函數:
添加
Vue.set(vm.userProfile, 'age', 27)
vm.$set(vm.userProfile, 'age', 27)
添加多個值時請使用新對象替換就對象的方式:
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
關于Vue中的事件綁定
v-on可以用于給事件綁定回調,回調可以是一個表達式:
HTML:
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
JS:
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
同樣可以是一個方法:
HTML:
<div id="example-2">
<!-- `greet` 是在下面定義的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
JS:
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 對象中定義方法
methods: {
greet: function (event) {
// `this` 在方法里指向當前 Vue 實例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
另外,greet方法是可以以JS形勢直接調用的
example2.greet()
由于此種特性,事件綁定還有一種變形:
HTML:
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
JS:
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
此外,Vue為v-on提供了一系列事件修飾符,用來定制事件的傳遞
<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件監聽器時使用事件捕獲模式 -->
<!-- 即元素自身觸發的事件先在此處處理,然后才交由內部元素進行處理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只當在 event.target 是當前元素自身時觸發處理函數 -->
<!-- 即事件不是從內部元素觸發的 -->
<div v-on:click.self="doThat">...</div>
<!-- 點擊事件將只會觸發一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滾動事件的默認行為 (即滾動行為) 將會立即觸發 -->
<!-- 而不會等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>
按鍵修飾符
- .enter
- .tab
- .delete (捕獲“刪除”和“退格”鍵)
- .esc
- .space
- .up
- .down
- .left
- .right
<!-- 同上 -->
<input v-on:keyup.enter="submit">
v-model
輸入框
HTML:
<div class="app-8">
<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>
</div>
JS:
var app8 = new Vue({
el: '.app-8',
data: {
message: 'Hello Vue!'
}
})
復選框(單個):
HTML:
<div class = "app-9">
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
</div>
JS:
var app9 = new Vue({
el: '.app-9',
data: {
checked: true
}
})
復選框(多個):
HTML:
<div class='app-10'>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>
</div>
JS:
var app10 = new Vue({
el: '.app-10',
data: {
checkedNames: []
}
})
單選框:
HTML:
<div class="app-11">
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>Picked: {{ picked }}</span>
</div>
JS:
var app11 = new Vue({
el: '.app-11',
data: {
picked:""
}
})
選擇框:
HTML:
<div id="app-12">
<select v-model="selected">
<option disabled value="">請選擇</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
JS:
new Vue({
el: '#app-12',
data: {
selected: ''
}
})
如果 v-model 表達式的初始值未能匹配任何選項,<select> 元素將被渲染為“未選中”狀態。在 iOS 中,這會使用戶無法選擇第一個選項。因為這樣的情況下,iOS 不會觸發 change 事件。因此,更推薦像上面這樣提供一個值為空的禁用選項。
上述選擇也可以用v-for來動態創建
HTML:
<div class = "app-13">
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
</div>
JS:
new Vue({
el: '.app-13',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
動態綁定的value值:
<!-- 當選中時,`picked` 為字符串 "a" -->
<input type="radio" v-model="picked" value="a">
<!-- `toggle` 為 true 或 false -->
<input type="checkbox" v-model="toggle">
<!-- 當選中第一個選項時,`selected` 為字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
修飾符
.lazy
在默認情況下,v-model 在每次 input 事件觸發后將輸入框的值與數據進行同步 (除了上述輸入法組合文字時)。你可以添加 lazy 修飾符,從而轉變為使用 change 事件進行同步:
<!-- 在“change”時而非“input”時更新 -->
<input v-model.lazy="msg" >
.number
如果想自動將用戶的輸入值轉為數值類型,可以給 v-model 添加 number 修飾符:
<input v-model.number="age" type="number">
這通常很有用,因為即使在 type="number" 時,HTML 輸入元素的值也總會返回字符串。
.trim
如果要自動過濾用戶輸入的首尾空白字符,可以給 v-model 添加 trim 修飾符:
<input v-model.trim="msg">
組件
Vue注冊全局組件
Vue.component('my-component', {
// 選項
template: '<div>A custom component!</div>'
})
組件在注冊之后,便可以作為自定義元素 <my-component></my-component> 在一個實例的模板中使用。注意確保在初始化根實例之前注冊組件:
HTML:
<div id="example">//(此處需要有根實例引用)
<my-component></my-component>
</div>
JS:
// 注冊
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 創建根實例(此處必須常見根實例,否則無法使用自定義組件)
new Vue({
el: '#example'
})
局部注冊
你不必把每個組件都注冊到全局。你可以通過某個 Vue 實例/組件的實例選項 components 注冊僅在其作用域中可用的組件:
var Child = {
template: '<div>A custom component!</div>'
}
new Vue({
// ...
components: {
// <my-component> 將只在父組件模板中可用
'my-component': Child
}
})
data 必須是函數
構造 Vue 實例時傳入的各種選項大多數都可以在組件里使用。只有一個例外:data 必須是函數。
HTML:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
JS:
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// 技術上 data 的確是一個函數了,因此 Vue 不會警告,
// 但是我們卻給每個組件實例返回了同一個對象的引用
data: function () {
return { counter: 0 }
}
})
new Vue({
el: '#example-2'
})
使用Prop傳遞數據
組件實例的作用域是孤立的。這意味著不能 (也不應該) 在子組件的模板內直接引用父組件的數據。父組件的數據需要通過 prop 才能下發到子組件中。
子組件要顯式地用 props 選項聲明它預期的數據:
HTML:
<!-- HTML 特性是不區分大小寫的。所以,當使用的不是字符串模板時,camelCase (駝峰式命名) 的 prop 需要轉換為相對應的 kebab-case (短橫線分隔式命名) -->
<child my-message="hello!"></child>
JS:
Vue.component('child', {
// 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
關于data與Prop
Prop更像是對外屬性,外界可以使用,而data中的字段只能作為私有屬性供組件自身使用。
動態Prop綁定
HTML:
<div id="prop-example-2">
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
JS:
new Vue({
el: '#prop-example-2',
data: {
parentMsg: 'Message from parent'
}
})
單向數據流
Prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是反過來不會。這是為了防止子組件無意間修改了父組件的狀態,來避免應用的數據流變得難以理解。
另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應該在子組件內部改變 prop。如果你這么做了,Vue 會在控制臺給出警告。
在兩種情況下,我們很容易忍不住想去修改 prop 中數據:
Prop 作為初始值傳入后,子組件想把它當作局部數據來用;
Prop 作為原始數據傳入,由子組件處理成其它數據輸出。
對這兩種情況,正確的應對方式是:
1.定義一個局部變量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
2.定義一個計算屬性,處理 prop 的值并返回:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
注意在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,如果 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。
Prop驗證
我們可以為組件的 prop 指定驗證規則。如果傳入的數據不符合要求,Vue 會發出警告。這對于開發給他人使用的組件非常有用。
要指定驗證規則,需要用對象的形式來定義 prop,而不能用字符串數組:
Vue.component('example', {
props: {
// 基礎類型檢測 (`null` 指允許任何類型)
propA: Number,
// 可能是多種類型
propB: [String, Number],
// 必傳且是字符串
propC: {
type: String,
required: true
},
// 數值且有默認值
propD: {
type: Number,
default: 100
},
// 數組/對象的默認值應當由一個工廠函數返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數
propF: {
validator: function (value) {
return value > 10
}
}
}
})
type 可以是下面原生構造器:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
type 也可以是一個自定義構造器函數,使用 instanceof 檢測。
當 prop 驗證失敗,Vue 會拋出警告 (如果使用的是開發版本)。注意 prop 會在組件實例創建之前進行校驗,所以在 default 或 validator 函數里,諸如 data、computed 或 methods 等實例屬性還無法使用。
非Prop特性
盡管為組件定義明確的 prop 是推薦的傳參方式,組件的作者卻并不總能預見到組件被使用的場景。所以,組件可以接收任意傳入的特性,這些特性都會被添加到組件的根元素上。
<bs-date-input data-3d-date-picker="true"></bs-date-input>
添加屬性 data-3d-date-picker="true" 之后,它會被自動添加到 bs-date-input 的根元素上。
自定義事件
父控件通過Prop向子控件傳遞數據,那么子控件又如何與父控件通信呢?
我們可以通過父控件監聽子控件事件來實現這一需求,那么就要借助事件接口:
- 使用 $on(eventName) 監聽事件
- 使用 $emit(eventName, optionalPayload) 觸發事件
eg.:
HTML:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
JS:
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
非父子組件間通信
事實上$emit和$on只是向我們提供了事件監聽的接口,并沒有將范圍限制在父子控件間,在簡單的場景下,可以使用一個空的 Vue 實例作為事件總線:
var bus = new Vue()
// 觸發組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創建的鉤子中監聽事件
bus.$on('id-selected', function (id) {
// ...
})