[TOC]
- Vue 學(xué)習(xí)筆記
- Vue 源碼解析 - 主線流程
- Vue 源碼解析 - 模板編譯
- Vue 源碼解析 - 組件掛載
- Vue 源碼解析 - 數(shù)據(jù)驅(qū)動與響應(yīng)式原理
簡介
Vue 是一個用于構(gòu)建 Web 用戶界面的漸進式 JavaScript 框架囱怕。其核心庫只關(guān)注視圖層(view layer)耙饰,同時具備良好的第三方支持庫生態(tài)用以應(yīng)付構(gòu)建復(fù)雜大型單頁應(yīng)用(SPA:Single-Page Application)。
MVVM 模型
Vue 雖然沒有完全遵循 MVVM(Model-View-ViewModel)模型塑崖,當其設(shè)計也受到了 MVVM 的啟發(fā),以數(shù)據(jù)驅(qū)動界面惹苗,如下圖所示:
在 Vue 中殿较,充當 ViewModel 的是一個 Vue 實例(new Vue({})
),該 Vue實例 作用于某一個 HTML 元素上桩蓉,全權(quán)代理該元素節(jié)點的所有操作淋纲。
Vue實例 內(nèi)部通過 DOM Listeners 可以觀測到頁面上 DOM 元素的變化,從而將該種變化同步更改到 Model 中的對應(yīng)數(shù)據(jù)院究。
同時通過 Data Bindings洽瞬,當 Model 中的數(shù)據(jù)改變時,則會對相應(yīng)視圖上的顯示進行更改业汰,從而實現(xiàn)了 View 和 Model 的數(shù)據(jù)雙向綁定伙窃。
注:傳統(tǒng)的 Web 編程模型是 結(jié)構(gòu)驅(qū)動,即要對一個 DOM 節(jié)點進行操作样漆,第一步就是要獲取該 DOM 節(jié)點對象对供,然后再修改數(shù)據(jù)更新到節(jié)點上。
而 Vue 的中心思想是 數(shù)據(jù)驅(qū)動氛濒,要更改界面产场,其實就是要更改數(shù)據(jù)。
簡而言之舞竿,在 Vue 中京景,不應(yīng)當考慮操作 DOM,而是專注于 操作數(shù)據(jù)骗奖。
安裝
Vue 的安裝有多種方法确徙,這里主要介紹兩種方法:
- 通過
<script>
標簽直接引入:
<!-- 對于制作原型或?qū)W習(xí),你可以這樣使用最新版本 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 或者-->
<!-- 對于生產(chǎn)環(huán)境执桌,我們推薦鏈接到一個明確的版本號和構(gòu)建文件鄙皇,以避免新版本造成的不可預(yù)期的破壞:-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
注:通過<script>
標簽引入,Vue
會被注冊為一個全局變量
- 直接使用官方提供的快速搭建復(fù)雜單頁面應(yīng)用 (SPA) 的腳手架 vue-cli:
- 首先全局安裝該腳手架 vue-cli:
npm install -g @vue/cli
vue create <project-name>
注:使用 vue-cli 前需確保系統(tǒng)已安裝 nodejs仰挣。
以上兩步操作完成伴逸,我們便創(chuàng)建完成一個 Vue 項目。
在項目的 package.json
中膘壶,可以看到 vue-cli 提供了兩個腳本命令讓我們運行與打包項目:
-
npm run serve
:運行項目 -
npm run build
:打包項目到 dist 文件夾
組件化
Vue 的兩大特性為 數(shù)據(jù)驅(qū)動 和 組件化错蝴。
通常一個大的頁面可以劃分為許多個小區(qū)塊,這些小區(qū)塊有些結(jié)構(gòu)是相似的颓芭,我們可以將這些相似的區(qū)塊抽象出一個統(tǒng)一的結(jié)構(gòu)顷锰,方便復(fù)用,這種抽象結(jié)構(gòu)的方法即稱為組件化亡问。
在實際項目開發(fā)中官紫,一個大的頁面通常都是由許多個小的組件構(gòu)造而成的,如下圖所示:
Vue 提供了兩種組件定義的方式:
-
全局組件:全局組件只需定義一次,便可被其他任意組件使用束世。
定義方式:Vue.component(id, [definition])
<body>
<div id="app">
<my-component />
</div>
<script>
const myComponent = Vue.component('my-component',{
data(){
return {
'message': 'Hello Global Component'
}
},
template:`
<h1>{{message}}</h1>
`
});
const vm = new Vue({
el: "#app",
});
</script>
</body>
-
局部組件:對于全局組件來說悼吱,即使頁面沒有使用該組件,組件也會被注入到最終的構(gòu)建結(jié)果中良狈,導(dǎo)致了 JavaScript 文件的無謂增加后添。而局部組件可以做到按需加載,需要哪些組件薪丁,按需引入即可遇西,更加靈活高效。
定義方式:通過一個普通的 JavaScript 對象來定義組件严嗜,然后 Vue實例 按需引入需要的組件即可粱檀。
<body>
<div id="app">
<component-a></component-a>
<component-b />
</div>
<script>
const componentA = {
template: `
<h1>Component A</h1>
`
};
const componentB = {
template: `
<h1>Component B</h1>
`
};
const vm = new Vue({
el: '#app',
components: { // 按需引入需要的組件
componentA,
componentB
}
});
</script>
</body>
最佳實踐:在 Vue 中,組件通常都定義到一個單獨的.vue
文件中漫玄,其他組件需要時茄蚯,導(dǎo)入相應(yīng)組件的.vue
文件即可。
// MyComponent.vue
<template>
<h1>{{message}}</h1>
</template>
<script>
export default {
name: "MyCompoent",
data() {
return {
message: "Hello MyComponent!"
};
}
};
</script>
<style scoped>
h1 {
background: red;
}
</style>
可以直接使用以下命令直接運行.vue
文件睦优,查看組件展示效果:
vue serve MyComponent.vue --open
也可以在其他組件內(nèi)導(dǎo)入該組件渗常,進行使用:
<template>
<div id="app">
<h1>Parent Component</h1>
<!-- 使用組件 -->
<my-component />
</div>
</template>
<script>
// 導(dǎo)入組件
import MyComponent from "./MyComponent.vue";
export default {
name: "app",
components: {
MyComponent // 引入組件
}
};
</script>
注:在 Vue 中,組件實質(zhì)是帶有一個名字的 Vue實例汗盘,其性質(zhì)與 Vue實例 基本一致(遵循 Vue實例 的生命周期等內(nèi)容)皱碘,特點是多了個組件復(fù)用功能。
Vue實例
Vue 實例充當 ViewModel 角色隐孽,負責(zé) View 和 Model 之間的數(shù)據(jù)綁定:
new Vue(Options)
當創(chuàng)建一個 Vue 實例時癌椿,你可以傳入一個選項對象Options
,該Options
的選項列表有如下可選:
下面列舉一些Options
常用選項
-
選項 / 數(shù)據(jù):
data
描述:Vue 實例的數(shù)據(jù)對象菱阵,用于數(shù)據(jù)的存儲與顯示踢俄。
類型:Object | Function
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "Hello Vue!"
}
});
</script>
</body>
注:Vue 將會遞歸將data
的屬性轉(zhuǎn)換為getter/setter
,從而讓data
的屬性能夠響應(yīng)數(shù)據(jù)變化晴及。比如在控制臺輸入vm.message = 'Hi Vue!!!
都办,可以觀察到頁面數(shù)據(jù)發(fā)生了更改。
注:組件 中的data
屬性必須是Fcuntion
類型抗俄,其返回一個Object
脆丁,原因是組件復(fù)用時世舰,保證每個新組件都有獨一的一份數(shù)據(jù)拷貝动雹。
// MyComponent.vue
<template>
<h1>{{message}}</h1>
</template>
<script>
export default {
name: 'MyComponent',
data(){ // 函數(shù)類型
return { // 返回數(shù)據(jù)對象
'message': 'Hello Vue!'
}
}
}
</script>
-
選項 / 數(shù)據(jù):
props
描述:該屬性用于接收來自父組件的數(shù)據(jù)。
類型:Array<string> | Object
跟压。
當傳遞的是Object
類型時胰蝠,則可以基于對象的語法使用以下選項:
?type
:指定數(shù)據(jù)類型,該值可以為原生類型(String
,Number
茸塞,Boolean
躲庄,Array
,Object
钾虐,Date
噪窘,Function
,Symbol
)效扫,自定義構(gòu)造函數(shù)倔监,或上述內(nèi)容組成的數(shù)組。
?default:any
:為該prop
指定一個默認值菌仁。如果該prop
沒有被傳入浩习,則使用該默認值。對象或數(shù)組的默認值必須從一個工廠函數(shù)返回济丘。
?required: Boolean
:定義該prop
是否為必填項谱秽。
?validator: Function
:自定義驗證函數(shù),對該prop
進行校驗摹迷。
Vue.component('my-component', {
props: {
// 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
propA: Number,
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認值的數(shù)字
propD: {
type: Number,
default: 100
},
// 帶有默認值的對象
propE: {
type: Object,
// 對象或數(shù)組默認值必須從一個工廠函數(shù)獲取
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數(shù)
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
-
選項 / 數(shù)據(jù):
propsData
描述:創(chuàng)建實例時傳遞props
疟赊。主要作用是方便測試。
類型:{ [key: string]: any }
var Comp = Vue.extend({
props: ['msg'],
template: '<div>{{ msg }}</div>'
})
var vm = new Comp({
propsData: {
msg: 'hello'
}
})
注:propsData
屬性只能用于new
創(chuàng)建的實例中峡碉。
-
選項 / 數(shù)據(jù):
methods
:
描述:方法定義听绳,Vue實例 可以直接訪問這些方法,或在指令表達式中直接調(diào)用這些方法异赫。
類型:{ [key: string]: Function }
var vm = new Vue({
data: { a: 1 },
methods: {
plus: function () {
this.a++
}
}
})
vm.plus()
vm.a // 2
注:methods
中的this
自動綁定到當前 Vue實例椅挣。
-
選項 / 數(shù)據(jù):
computed
描述:計算屬性,主要用于對data
數(shù)據(jù)進行計算轉(zhuǎn)換塔拳。
類型:{ [key: string]: Function | { get: Function, set: Function } }
<template>
<div>
<h1>獲取數(shù)據(jù): {{computedData}}</h1>
<h1>設(shè)置數(shù)據(jù):{{setData = 3}}</h1>
<h1>獲取數(shù)據(jù): {{setData}}</h1>
</div>
</template>
<script>
export default {
name: "Computed",
data() {
return {
count: 1
};
},
computed: {
// 只讀取 data
computedData() {
return this.count + 10;
},
// 讀取和設(shè)置 data
setData: {
get() {
return this.count + 1;
},
set(value) {
this.count = value;
}
}
}
};
</script>
注:computed
類型為Object
鼠证,其具有如下特點:
-
computed
內(nèi)部定義的屬性為訪問器屬性,即具備getter
和setter
靠抑,且其內(nèi)部this
自動綁定到當前 Vue實例量九。 -
computed
會自動 緩存 計算結(jié)果,只有當依賴的響應(yīng)式屬性變化時颂碧,computed
才會重新進行計算荠列。
緩存 是computed
與methods
的最大區(qū)別之處,methods
每次調(diào)用一定會運行函數(shù)载城,而computed
則不一定肌似。
-
選項 / 數(shù)據(jù):
watch
:
描述:偵聽屬性,用于監(jiān)控data
或computed
的數(shù)據(jù)诉瓦,當數(shù)據(jù)變更時進行回調(diào)通知川队。
類型:{ [key: string]: string | Function | Object | Array }
注:watch
類型為Object
力细,其內(nèi)部屬性的類型有多種:string | Function | Object | Array
,這里簡單介紹 3 種:
-
string
:字符串表示回調(diào)函數(shù)名固额,當數(shù)據(jù)改變時眠蚂,回調(diào)該函數(shù):
const vm = new Vue({
el: '#app',
data: {
a: 1
},
methods: {
aChanged(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
}
},
watch: {
a: 'aChanged'
}
});
-
Function
:當數(shù)據(jù)改變時,直接回調(diào)該函數(shù):
const vm = new Vue({
el: '#app',
data: {
a: 1
},
watch: {
a(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
}
}
});
-
Object
:對監(jiān)控的屬性為對象時斗躏,Vue 默認只能監(jiān)控到對象重新被賦值的變化逝慧,而如果需要監(jiān)聽對象內(nèi)部屬性的變化,則可使用該選項啄糙,其中:
handler
代表回調(diào)函數(shù)馋艺。
deep
用來控制監(jiān)聽對象屬性的層級,deep=true
時只要對象內(nèi)部 property 改變(不管嵌套有多深)迈套,都會監(jiān)聽到捐祠。
immediate
用來設(shè)置是否立即產(chǎn)生回調(diào)。當immediate=true
時桑李,回調(diào)函數(shù)會立即被調(diào)用踱蛀,傳遞的是屬性當前的值。
const vm = new Vue({
el: '#app',
data: {
a: {
aa: {
aaa: 3
}
}
},
watch: {
a: {
handler(value, oldValue) {
console.log(`a changed: new:${value} --> old:${oldValue}`);
},
deep: true // 被監(jiān)聽對象的 property 改變時被調(diào)用贵白,無論嵌套的有多深
}
}
});
注:大多數(shù)情況下率拒,觀察和響應(yīng)數(shù)據(jù)變更使用計算屬性(computed
)便足夠了,但是當在數(shù)據(jù)變化時需要執(zhí)行異步或開銷較大的操作時禁荒,則此時使用偵聽屬性(watch
)會更加適合猬膨。
new Vue({
el: '#app'
});
注:如果在實例化時存在這個選項,實例將立即進入編譯過程呛伴,否則勃痴,需要顯式調(diào)用vm.$mount()
手動開啟編譯。
<div id="app"></div>
const vm = new Vue({
el: '#app',
template: '<h1>template</h1>' // <div> 會被 <h1> 完全覆蓋
})
-
選項 / DOM:
render
描述:字符串模板的代替方案,允許你發(fā)揮 JavaScript 最大的編程能力姐军。該渲染函數(shù)接收一個createElement
方法作為第一個參數(shù)用來創(chuàng)建VNode
铁材。
類型:(createElement: () => VNode) => VNode
<div id="app"></div>
new Vue({
render(createElement) {
return createElement('div', {
class: 'rendered'
},
[
createElement('h1', {
domProps: {
innerHTML: 'div>h1 rendered by vue'
}
})
]
);
}}).$mount('#app');
注:Vue 推薦在絕大多數(shù)情況下使用模板來創(chuàng)建你的 HTML,只有在一些特殊場景下奕锌,比如模板冗長且具備重復(fù)元素著觉,則此時使用渲染函數(shù)render
通過編寫 JavaScript 代碼來渲染出頁面會更加方便簡潔。
- 選項 / 生命周期鉤子:見下文
生命周期
每個 Vue實例 在掛載到頁面時惊暴,都會經(jīng)歷一系列的初始化過程饼丘,例如,需要設(shè)置數(shù)據(jù)監(jiān)聽缴守、編譯模板葬毫、將實例掛載到 DOM 并在數(shù)據(jù)變化時更新 DOM 等镇辉。在創(chuàng)建 Vue實例 的這整個過程中屡穗,Vue 為我們預(yù)留出了一些 Hook 點贴捡,方便我們在 Vue實例 創(chuàng)建過程的某個生命周期中進行一些操作。如下圖所示:
注:圖片來源于網(wǎng)上村砂,侵刪烂斋。
這些預(yù)留的生命周期鉤子函數(shù)總共有如下幾個:
beforeCreate
:Vue實例 初始化之后,此時data
和methods
中的數(shù)據(jù)還未進行初始化础废,因此無法獲取汛骂。created
:表示 Vue實例 創(chuàng)建完成,但還未掛載到頁面上评腺,此時data
和methods
都已經(jīng)初始化成功帘瞭,可以對其進行調(diào)用獲取,而掛載階段未開始蒿讥,所以$el
屬性目前不可見蝶念。beforeMount
:在掛載開始之前被調(diào)用,此時模板已在內(nèi)存中被編譯完成芋绸,只是尚未掛載到頁面上媒殉,因此,此時頁面上顯示的還是未渲染的結(jié)構(gòu)摔敛。mounted
:掛載完成廷蓉,此時頁面會顯示我們渲染的視圖。如果想要操作頁面上的 DOM 節(jié)點马昙,最早的時間就是該處桃犬。
注:mounted
不會 承諾所有的子組件也都一起被掛載。如果你希望等到整個視圖都渲染完畢行楞,可以用vm.$nextTick
替換掉mounted
:
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
beforeUpdate
:數(shù)據(jù)更新時調(diào)用疫萤,此時內(nèi)存中data
數(shù)據(jù)已更新,但頁面中顯示的數(shù)據(jù)還未更新敢伸,數(shù)據(jù)與頁面不同步扯饶。這里適合在更新之前訪問現(xiàn)有的 DOM,比如手動移除已添加的事件監(jiān)聽器池颈。updated
:新數(shù)據(jù)成功渲染到頁面尾序,此時數(shù)據(jù)與頁面處于同步狀態(tài)。
注:updated
不會 承諾所有的子組件也都一起被掛載躯砰。如果你希望等到整個視圖都渲染完畢每币,可以用vm.$nextTick
替換掉mounted
:activated
:激活狀態(tài),表示當前組件處于前臺頁面琢歇,用戶可與該組件進行交互兰怠。
注:只有組件被內(nèi)置組件keep-alive
包裹時梦鉴,該鉤子才有可能被調(diào)用。
<template>
<keep-alive>
<my-component />
</keep-alive>
</template>
deactivated
:停用狀態(tài)揭保,表示當前組件處于后臺頁面肥橙,用戶不能與之交互。
注:只有組件被內(nèi)置組件keep-alive
包裹時秸侣,該鉤子才有可能被調(diào)用存筏。beforeDestroy
:實例銷毀之前調(diào)用。在這一步味榛,實例仍然完全可用椭坚,即此時實例的data
,methods
等所有數(shù)據(jù)完全可用搏色。destroyed
:Vue 實例銷毀后調(diào)用善茎。此時 Vue實例 指向的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除频轿,所有的子實例也會被銷毀垂涯。errorCaptured
:當捕獲一個來自子孫組件的錯誤時被調(diào)用。此鉤子會收到三個參數(shù):錯誤對象略吨、發(fā)生錯誤的組件實例以及一個包含錯誤來源信息的字符串集币。此鉤子可以返回false
以阻止該錯誤繼續(xù)向上傳播。
指令
指令 (Directives) 是帶有v-
前綴的特殊屬性翠忠。
Vue 提供了以下內(nèi)置的指令:
-
v-text
:更新元素的textContent
鞠苟,該指令與使用{{ Mustache }}
插值效果一樣。
類型:string
<span v-text="msg"></span>
<!-- 和下面的一樣 -->
<span>{{msg}}</span>
-
v-html
:更新元素的innerHTML
秽之。
類型:string
<template>
<div id="app">
<div v-html="html"></div>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
html: '<a >baidu</a>'
};
},
};
</script>
注:v-html
的內(nèi)容只會按普通 HTML 插入当娱,不會作為 Vue 模板進行編譯。
注:在單文件組件里考榨,scoped
的樣式不會應(yīng)用在 v-html
內(nèi)部跨细,因為那部分 HTML 沒有被 Vue 的模板編譯器處理。
注:在網(wǎng)站上動態(tài)渲染任意 HTML 是非常危險的河质,因為容易導(dǎo)致 XSS 攻擊冀惭。只在可信內(nèi)容上使用v-html
,永不用在用戶提交的內(nèi)容上掀鹅。
-
v-show
:條件渲染散休,根據(jù)表達式之真假值,切換元素的display
CSS 屬性乐尊。
類型:any
<h1 v-show="ok">Hello!</h1>
注:v-show
不支持<template>
元素戚丸,也不支持v-else
。
-
v-if
:條件渲染扔嵌,根據(jù)表達式的值的真假條件渲染元素限府。在切換時元素及它的數(shù)據(jù)綁定 / 組件被銷毀并重建夺颤。如果元素是<template>
,將提出它的內(nèi)容作為條件塊。
類型:any
<h1 v-if="awesome">Vue is awesome!</h1>
注:當和v-if
一起使用時,v-for
的優(yōu)先級比v-if
更高。
注:v-show = false
時只是把元素設(shè)置為:display:none
,元素還留著 DOM 樹上必搞。
而v-if = false
時,元素會被整個移除没佑,其上綁定的數(shù)據(jù)/組件都會被銷毀原叮。
-
v-else
:v-if
或v-if-else
的分支。
類型:無
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
-
v-else-if
:v-if
的分支咱台。
類型:any
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
-
v-for
:遍歷源數(shù)據(jù),渲染元素列表回溺。
類型:Array | Object | number | string | Iterable
<div v-for="item in items">
{{ item.text }}
</div>
<!-- 另外也可以為數(shù)組索引指定別名 (或者用于對象的鍵):-->
<div v-for="(item, index) in items"></div>
<div v-for="(val, key) in object"></div>
<div v-for="(val, name, index) in object"></div>
注:v-for
渲染元素時春贸,默認使用“就地更新”策略,即當列表數(shù)據(jù)改變時遗遵,Vue 不會移動當前 DOM 元素來重新匹配數(shù)據(jù)項萍恕,而是根據(jù)索引位置重新渲染數(shù)據(jù)。比如:
現(xiàn)在我們有數(shù)據(jù)項:
data() {
return {
datas: [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
]
}
將這些數(shù)據(jù)項渲染到頁面上:
<template>
<div>
<ul>
<li v-for="(item,index) in datas">
<input type="checkbox" />
{{item.name}}
</li>
</ul>
</template>
我們使用v-for
將每條數(shù)據(jù)項渲染到一個<li>
上车要,此時顯示效果如下:
如果此時我們勾選第一個<li>
的checkbox
允粤,即one
勾選上,然后再往數(shù)據(jù)列表前面添加一個數(shù)據(jù):this.datas.unshift({ id: 4, name: "four" })
翼岁,則可以看到顯示效果如下:
可以看到类垫,我們想要的是one
被勾選了,但是效果是數(shù)據(jù)列表首位被勾選琅坡。出現(xiàn)這種現(xiàn)象的原因就是v-for
默認采用的“就地更新”策略:它會復(fù)用已渲染完成的 DOM 元素悉患,然后只對變化的數(shù)據(jù)進行修改,比如這里復(fù)用了第一條<li><checkbox>one</li>
榆俺,添加數(shù)據(jù)項售躁,對第一條<li>
來說,他的數(shù)據(jù)改變了茴晋,但是<checkbox>
不包含在數(shù)據(jù)項里陪捷,因此只會修改數(shù)據(jù),將one
修改為four
晃跺,而checkbox
仍保持勾選狀態(tài)揩局。
因此,“就地更新”策略是高效的掀虎,但是 只適用于不依賴子組件狀態(tài)或臨時 DOM 狀態(tài)(例如:表單輸入值) 的列表渲染輸出凌盯。
而要解決上述問題付枫,只需為v-for
提供一個key
屬性(key
必須是唯一的),這樣 Vue 就可以識別出數(shù)據(jù)項對應(yīng)的渲染條目驰怎,從而重用和重新排序現(xiàn)有元素:
<template>
<div>
<ul>
<li v-for="(item,index) in datas" :key="item.id">
<input type="checkbox" />
{{item.name}}
</li>
</ul>
</template>
由于新添加的數(shù)據(jù)id=4
阐滩,當前已存在的<li>
沒有與之對應(yīng)的標識key
,因此 Vue 會重新渲染一個新的<li>
县忌,并將其與id=4
對應(yīng)起來掂榔,結(jié)果如下圖所示:
注:“就地更新”策略其實就是使用索引作為節(jié)點標識,即:key=index
症杏。
-
v-on
:綁定事件監(jiān)聽器装获。
縮寫:@
類型:Function | Inline Statement | Object
參數(shù):event
修飾符:
?.stop
:調(diào)用event.stopPropagation()
,停止事件分發(fā)厉颤。
?.prevent
:調(diào)用event.preventDefault()
穴豫,取消事件的默認動作。
?.capture
:添加事件偵聽器時使用capture
(捕獲)模式逼友。
?.self
:只有當事件是從偵聽器綁定的元素本身觸發(fā)時才觸發(fā)回調(diào)精肃。
?.{keyCode | keyAlias}
:只有當事件是從特定鍵觸發(fā)時才觸發(fā)回調(diào)。
?.native
:監(jiān)聽組件根元素的原生事件帜乞。
?.once
:只觸發(fā)一次回調(diào)司抱。
?.left
: 只有當點擊鼠標左鍵時觸發(fā)。
?.right
: 只有當點擊鼠標右鍵時觸發(fā)黎烈。
?.middle
: 只有當點擊鼠標中鍵時觸發(fā)习柠。
?.passive
:以{ passive: true }
模式添加偵聽器
<!-- 方法處理器 -->
<button v-on:click="doThis"></button>
<!-- 動態(tài)事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>
<!-- 內(nèi)聯(lián)語句 -->
<button v-on:click="doThat('hello', $event)"></button>
<!-- 縮寫 -->
<button @click="doThis"></button>
<!-- 動態(tài)事件縮寫 (2.6.0+) -->
<button @[event]="doThis"></button>
<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>
<!-- 阻止默認行為 -->
<button @click.prevent="doThis"></button>
<!-- 阻止默認行為,沒有表達式 -->
<form @submit.prevent></form>
<!-- 串聯(lián)修飾符 -->
<button @click.stop.prevent="doThis"></button>
<!-- 鍵修飾符怨喘,鍵別名 -->
<input @keyup.enter="onEnter">
<!-- 鍵修飾符津畸,鍵代碼 -->
<input @keyup.13="onEnter">
<!-- 點擊回調(diào)只會觸發(fā)一次 -->
<button v-on:click.once="doThis"></button>
<!-- 對象語法 (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
注:v-on
用在普通元素上時,只能監(jiān)聽 原生 DOM 事件必怜。用在自定義組件上時肉拓,也可以監(jiān)聽子組件觸發(fā)的自定義事件。
<my-component @my-event="handleThis"></my-component>
<!-- 內(nèi)聯(lián)語句 -->
<my-component @my-event="handleThis(123, $event)"></my-component>
<!-- 組件中的原生事件 -->
<my-component @click.native="onClick"></my-component>
-
v-bind
:動態(tài)綁定
縮寫::
類型:any (with argument) | Object (without argument)
參數(shù):attrOrProp (optional)
修飾符:
?.prop
:被用于綁定 DOM 屬性 (property)
?.camel
:將 kebab-case 特性名轉(zhuǎn)換為 camelCase(駝峰式)
?.sync
:會擴展成一個更新父組件綁定值的 v-on 偵聽器
<!-- 綁定一個屬性 -->
<img :src="imageSrc">
<!-- 動態(tài)特性名 (2.6.0+) -->
<button :[key]="value"></button>
<!-- class 綁定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">
<!-- style 綁定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>
<!-- 綁定一個有屬性的對象 -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- 通過 prop 修飾符綁定 DOM 屬性 -->
<div v-bind:text-content.prop="text"></div>
<!-- prop 綁定梳庆∨荆“prop”必須在 my-component 中聲明。-->
<my-component :prop="someThing"></my-component>
<!-- 通過 $props 將父組件的 props 一起傳給子組件 -->
<child-component v-bind="$props"></child-component>
<!-- 支持綁定駝峰命名屬性 -->
<svg :view-box.camel="viewBox"></svg>
-
v-model
:表單控件與數(shù)據(jù)屬性的雙向綁定膏执。
修飾符:
?.lazy
:使用<input>
的change
事件進行同步驻售。
?.number
:自動將字符串轉(zhuǎn)為數(shù)字。
?.trim
:輸入首尾空格過濾更米。
<input type="text" v-model="message" />
<script>
export default {
name: 'VModel',
data() {
return {
message: ''
};
}
};
</script>
-
v-slot
:插槽欺栗。
縮寫:#
當我們定義組件的時候,有些內(nèi)容可能需要由父組件傳入,因此迟几,此時可以使用插槽消请,預(yù)留出位置給到父組件進行自定義內(nèi)容傳入:
// 子組件:預(yù)留插槽
<template>
<div>
<h1>Son Component</h1>
<!-- 預(yù)留插槽 -->
<slot></slot>
</div>
</template>
// 父組件:傳入插槽內(nèi)容
<template>
<div>
<h1>Parent Component</h1>
<son-component>
<h2>slot: content from Parent Component</h2>
</son-component>
</div>
</template>
? 后備內(nèi)容:可以通過為<slot>
內(nèi)部提供默認內(nèi)容,只有當父組件顯示傳入內(nèi)容時类腮,才會覆蓋默認內(nèi)容:
<slot>
<h1>Default Content</h1>
</slot>
? 具名插槽:我們可以給插槽進行命名(使用name
屬性)臊泰,這樣父組件就可指定名字(使用v-slot
指令)對特定的插槽進行覆蓋:
// 子組件模板
<template>
<div>
<h1>Son Component</h1>
<!-- 預(yù)留命名插槽 -->
<slot name="header"></slot>
<main>
<!-- name="default" -->
<slot></slot>
</main>
<slot name="footer"></slot>
</div>
</template>
// 父組件
<template>
<div>
<h1>Parent Component</h1>
<son-component>
<template v-slot:header>
<h2>替換 header 插槽</h2>
</template>
<h3>替換默認插槽</h3>
<template #footer>
<h2>替換 footer 插槽</h2>
</template>
</son-component>
</div>
</template>
注:v-slot
只能添加在一個<template>
或 組件 上。
注:默認插槽其實也是一個具名插槽蚜枢,其名稱為:default
缸逃。
? 插槽 prop:使用 插槽 prop 可以傳遞子組件的數(shù)據(jù)給到父組件,使父組件可以在覆蓋插槽的內(nèi)容上使用子組件的數(shù)據(jù):
// 子組件
<slot :msg="message"></slot>
<script>
export default {
name: 'SonComponent',
data() {
return {
message: 'Hello from Son Component!'
};
}
};
</script>
// 父組件:slotProps 接收子組件的 插槽props
<son-component #default="slotProps">
{{slotProps.msg}}
</son-component>
-
v-pre
:跳過該元素及其子元素的編譯過程厂抽⌒杵担可以用來顯示原始 Mustache 標簽。
類型:無
<span v-pre>{{ this will not be compiled }}</span>
-
v-cloak
:這個指令保持在元素上直到關(guān)聯(lián)實例結(jié)束編譯修肠。通常結(jié)合 CSS 規(guī)則來達到隱藏未編譯的 Mustache 標簽直到實例準備完畢贺辰。
類型:無
[v-cloak] {
display: none;
}
<div v-cloak>
{{ message }}
</div>
-
v-once
:只渲染元素和組件 一次户盯。隨后的重新渲染嵌施,元素/組件及其所有的子節(jié)點將被視為靜態(tài)內(nèi)容并跳過。這可以用于優(yōu)化更新性能莽鸭。
<span v-once>This will never change: {{msg}}</span>
- 自定義指令:在 Vue2.0 中吗伤,代碼復(fù)用和抽象的主要形式是組件。然而硫眨,有的情況下足淆,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令礁阁。
Vue 提供了兩種自定義指令的方式:
-
全局指令:使用
Vue.directive
:
// 注冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {...})
-
局部指令:組件中定義一個
directives
屬性:
// 注冊一個局部自定義指令 `v-focus`
directives: {
focus: {...}
}
-
鉤子函數(shù):一個指令定義對象可以提供如下幾個鉤子函數(shù) (均為可選):
?bind
:指令第一次綁定到元素時調(diào)用巧号。改鉤子只會被調(diào)用一次,可在此做一些初始化設(shè)置姥闭。
?inserted
:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在丹鸿,但不一定已被插入文檔中)。
?update
:所在組件的 VNode 更新時調(diào)用棚品,但是可能發(fā)生在其子 VNode 更新之前靠欢。
?componentUpdated
:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用。
?unbind
:指令與元素解綁時調(diào)用铜跑。改鉤子只會被調(diào)用一次门怪,可在此做一些資源釋放操作。
示例:使用自定義指令v-customtext
模擬v-text
:
<template>
<h1 v-customtext="msg"></h1>
</template>
<script>
export default {
name: 'customeDirective',
data() {
return {
msg: 'Hello Custom Directives!'
};
},
directives: {
customtext: {
inserted(el, binding, vnode, oldVnode) {
el.innerText = binding.value;
}
}
}
};
</script>
其他
- 組件間通信:
? 父傳子:子組件通過props
屬性可接收父組件傳遞過來的變量:
// ParentComponent.vue
<template>
<son-component :msg="message" />
</template>
<script>
import SonComponent from './SonComponent';
export default {
name: 'ParentComponent',
components: {
SonComponent
},
data() {
return {
message: 'data from Parent Component'
};
}
};
</script>
// SonComponent.vue
<template>
<h1>{{msg}}</h1>
</template>
<script>
export default {
name: 'SonComponent',
props: {
msg: {
type: String,
required: true
}
}
};
</script>
? 子傳父:子組件可以通過$emit
發(fā)送自定義事件向父組件傳值锅纺,父組件直接注冊接收該事件即可:
// SonComponent.vue
<template>
<button @click="sendEvent">點擊發(fā)送事件</button>
</template>
<script>
export default {
name: 'SonComponent',
methods: {
sendEvent() {
// 發(fā)送自定義事件
this.$emit('eventFromChild', 'data from Son Component!!');
}
}
};
</script>
// ParentComponent.vue
<template>
<div>
<!-- 接收事件 -->
<son-component @eventFromChild="recvChildEvent" />
<h1>{{data}}</h1>
</div>
</template>
<script>
import SonComponent from './SonComponent';
export default {
name: 'ParentComponent',
components: {
SonComponent
},
data() {
return {
data: 'hhhh'
};
},
methods: {
recvChildEvent(data) {
this.data = data;
}
}
};
</script>
? 父傳子孫:父組件/祖先組件通過provide
提供變量掷空,子孫組件通過inject
來接收該變量:
// ParentComponent
import SonComponent from './SonComponent.vue'
export default {
name: 'ParentComponent',
components: {
SonComponent
},
provide: {
message: 'data from Parent Component'
}
}
// SonComponent
<template>
<h1>{{message}}</h1>
</template>>
<script>
export default {
name: 'SonComponent',
inject: ['message']
}
</script>>
更多組件間通信方式,請參考:Vue組件間通信6種方式