詳解vue高級特性
Vue為我們提供了很多高級特性,學(xué)習(xí)和掌握它們有助于提高你的代碼水平。
一、watch進(jìn)階
從我們剛開始學(xué)習(xí)Vue的時候,對于偵聽屬性,都是簡單地如下面一般使用:
watch:{
a(){
//doSomething
}
}
實際上,Vue對watch提供了很多進(jìn)階用法。
handler函數(shù)
以對象和handler函數(shù)的方式來定義一個監(jiān)聽屬性,handler就是處理監(jiān)聽變動時的函數(shù):
watch:{
a:{
handler:'doSomething'
}
},
methods:{
doSomething(){
//當(dāng) a 發(fā)生變化的時候,做些處理
}
}
handler有啥用?是多此一舉么?用途主要有兩點:
1 將處理邏輯抽象出去了,以method的方式被復(fù)用
2 給定義下面兩個重要屬性留出了編寫位置
deep屬性
不知道你注意到了沒有?
當(dāng)watch的是一個Object類型的數(shù)據(jù),如果這個對象內(nèi)部的某個值發(fā)生了改變,并不會觸發(fā)watch動作!
也就是說,watch默認(rèn)情況下,不監(jiān)測內(nèi)部嵌套數(shù)據(jù)的變動。但是很多情況下,我們是需要監(jiān)測的!
為解決這一問題,就要使用deep屬性:
watch:{
obj:{
handler:'doSomething',
deep:true
}
},
methods:{
doSomething(){
//當(dāng) obj 發(fā)生變化的時候,做些處理
}
}
deep屬性默認(rèn)為false,也就是我們常用的watch模式。
immediate屬性
watch 的handler函數(shù)通常情況下只有在監(jiān)聽的屬性發(fā)生改變時才會觸發(fā)。
但有些時候,我們希望在組件創(chuàng)建后,或者說watch被聲明和綁定的時候,立刻執(zhí)行一次handler函數(shù),這就需要使用immediate屬性了,它默認(rèn)為false,改為true后,就會立刻執(zhí)行handler。
watch:{
obj:{
handler:'doSomething',
deep:true,
immediate:true
}
},
methods:{
doSomething(){
//當(dāng) obj 發(fā)生變化的時候,做些處理
}
}
同時執(zhí)行多個方法
使用數(shù)組可以設(shè)置多項,形式包括字符串、函數(shù)、對象
watch: {
// 你可以傳入回調(diào)數(shù)組,它們會被逐一調(diào)用
a: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
}
二、$event的不同表現(xiàn)
$event 是事件對象的特殊變量,在兩種場景下,它有不同的意義,代表不同的對象。
1 在原生事件中表示事件本身??梢酝ㄟ^$event.target獲得事件所在的DOM對象,再通過value進(jìn)一步獲取具體的值。
<template>
<div>
<input type="text" @input="inputHandler('hello', $event)" />
</div>
</template>
export default {
methods: {
inputHandler(msg, e) {
console.log(e.target.value)
}
}
}
2 而在父子組件通過自定義事件進(jìn)行通信時,表示從子組件中傳遞出來的參數(shù)值
看下面的例子:
//blog-post組件的模板
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
在父級組件監(jiān)聽這個事件的時候,可以通過 $event 訪問到blog-post子組件傳遞出來的0.1這個值:
<blog-post ... v-on:enlarge-text="postFontSize += $event" ></blog-post>
此時,$event的值就是0.1,而不是前面的事件對象。
三、異步更新隊列
1 Vue 在更新 DOM 時是異步執(zhí)行的。
2 只要偵聽到數(shù)據(jù)變化,Vue 將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更。
3 如果同一個 watcher 被多次觸發(fā),只會被推入到隊列中一次。
這種在緩沖時去除重復(fù)數(shù)據(jù)對于避免不必要的計算和 DOM 操作是非常重要的。然后,在下一個的事件循環(huán)“tick”中,Vue 刷新隊列并執(zhí)行實際 (已去重的) 工作。Vue 在內(nèi)部對異步隊列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執(zhí)行環(huán)境不支持,則會采用 setTimeout(fn, 0) 代替。
例如,當(dāng)你設(shè)置 vm.someData = 'new value',該組件不會立即重新渲染。當(dāng)刷新隊列時,組件會在下一個事件循環(huán)“tick”中更新。
多數(shù)情況我們不需要關(guān)心這個過程,但是如果你想基于更新后的 DOM 狀態(tài)來做點什么,這就可能會有些棘手。
雖然 Vue.js 通常鼓勵開發(fā)人員使用“數(shù)據(jù)驅(qū)動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。為了在數(shù)據(jù)變化之后等待 Vue 完成更新 DOM,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback)。
這樣回調(diào)函數(shù)將在 DOM 更新完成后被調(diào)用。例如:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改數(shù)據(jù)
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
在組件內(nèi)使用 vm.$nextTick() 實例方法特別方便,因為它不需要全局 Vue,并且回調(diào)函數(shù)中的 this 將自動綁定到當(dāng)前的 Vue 實例上:
因為 $nextTick() 返回一個 Promise 對象,所以你可以使用新的 ES2017 async/await 語法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
//在這里可以看出,message并沒有立刻被執(zhí)行
//要理解頁面刷新和代碼執(zhí)行速度的差別
//通常我們在頁面上立刻就能看到結(jié)果,那是因為一輪隊列執(zhí)行其實很快,感覺不出DOM刷新的過程和所耗費(fèi)的時間
//但對于代碼的執(zhí)行,屬于即刻級別,DOM沒更新就是沒更新,就是會有問題
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}
通俗的解釋:
1 Vue的DOM刷新機(jī)制是個異步隊列,并不是你想象中的立刻、馬上、即時更新!
2 這個異步隊列是一輪一輪的執(zhí)行并刷新
3 上面帶來的問題是,一些依賴DOM更新完畢才能進(jìn)行的操作(比如對新增加的DOM元素進(jìn)行事件綁定),無法立刻執(zhí)行,必須等待一輪隊列執(zhí)行完畢
4 最容易碰到上面問題的地方:created生命周期鉤子函數(shù)中對DOM進(jìn)行操作
5 解決辦法:使用this.nextTick(回調(diào)函數(shù))方法,將對DOM的操作作為它的回調(diào)函數(shù)使用。
四、函數(shù)式組件
因為傳統(tǒng)編寫模板的能力不足,我們引入了渲染函數(shù)createElement。我們又希望獲得更多的靈活度,于是引入了JSX。最后,我們發(fā)現(xiàn)有些簡單的模板可以更簡單更小巧的實現(xiàn),于是引入了函數(shù)式組件。Vue總是試圖為每一種場景提供不同的能力。
有這么一類組件,它的特點是:
1 比較簡單
2 沒有管理任何狀態(tài),也就是說無狀態(tài),沒有響應(yīng)式數(shù)據(jù)
3 沒有監(jiān)聽任何傳遞給它的狀態(tài)
4 沒有寫生命周期方法
5 本質(zhì)上只是一個接收一些prop的函數(shù)
6 沒有實例,沒有this上下文
那么這個組件可以定義為函數(shù)式組件。與普通組件相比,函數(shù)式組件是無狀態(tài)的,無法實例化,沒有任何的生命周期和方法,適合只依賴于外部數(shù)據(jù)的變化而變化的組件,因其輕量,渲染性能會有所提高。
創(chuàng)建函數(shù)式組件
以定義全局組件的方式
Vue.component('my-component', {
functional: true,
// Props 是可選的
props: {
// ...
},
// 為了彌補(bǔ)缺少的實例
// 提供第二個參數(shù)作為上下文
render: function (createElement, context) {
// ...
}
})
注意其中的functional: true,
在 Vue 2.3.0 或以上的版本中,你可以省略 props 選項,所有組件上的 attribute 都會被自動隱式解析為 prop。
當(dāng)使用函數(shù)式組件時,該引用將會是 HTMLElement,因為他們是無狀態(tài)的也是無實例的。
對于單文件組件,創(chuàng)建函數(shù)式組件的方式是在模板標(biāo)簽內(nèi),添加functional屬性
<template functional> ... </template> <script> ... </script> <style> ... </style>
最重要的context參數(shù)
因為無狀態(tài),沒有this上下文,所以函數(shù)式組件需要的一切都是通過 context 參數(shù)來傳遞,它是一個包括如下字段的對象:
props:提供所有 prop 的對象
children:VNode 子節(jié)點的數(shù)組
slots:一個函數(shù),返回了包含所有插槽的對象
scopedSlots:(2.6.0+) 一個暴露傳入的作用域插槽的對象。也以函數(shù)形式暴露普通插槽。
data:傳遞給組件的整個數(shù)據(jù)對象,作為 createElement 的第二個參數(shù)傳入組件
parent:對父組件的引用
listeners:(2.3.0+) 一個包含了所有父組件為當(dāng)前組件注冊的事件監(jiān)聽器的對象。這是 data.on 的一個別名。
injections:(2.3.0+) 如果使用了 inject 選項,則該對象包含了應(yīng)當(dāng)被注入的 property。
應(yīng)用場景
函數(shù)式組件的一個典型應(yīng)用場景是作為包裝組件,比如當(dāng)你碰到下面需求時:
程序化地在多個組件中選擇一個來代為渲染;
在將 children、props、data 傳遞給子組件之前操作它們。
下面是一個 smart-list 組件的例子,它能根據(jù)傳入 prop 的值來代為渲染更具體的組件:
var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }
Vue.component('smart-list', {
functional: true,
props: {
items: {
type: Array,
required: true
},
isOrdered: Boolean
},
render: function (createElement, context) {
function appropriateListComponent () {
var items = context.props.items
if (items.length === 0) return EmptyList
if (typeof items[0] === 'object') return TableList
if (context.props.isOrdered) return OrderedList
return UnorderedList
}
return createElement(
appropriateListComponent(),
context.data,
context.children
)
}
})
五、監(jiān)聽子組件的生命周期
假如我們有父組件Parent和子組件Child,如果在父組件中需要監(jiān)聽子組件的mounted這個生命周期函數(shù),并做一些邏輯處理,常規(guī)寫法可能如下:
// Parent.vue
<Child @mounted="doSth" />
//Child.vue
mounted(){
this.$emit('mounted');
}
但是,Vue給我們提供了一種更簡便的方法,子組件無需做任何處理,只需要在父組件引用子組件時使用@hook事件來監(jiān)聽即可,代碼如下:
// Parent.vue
<Child @hook:mounted="doSth" />
methods:{
doSth(){
//some codes here
}
}
核心是@hook:mounted="doSth"的寫法!
當(dāng)然這里不僅僅可以監(jiān)聽mounted,其他生命周期都可以監(jiān)聽,例如created、updated等。
六、樣式穿透
我們知道,在單文件組件的style中使用 scoped 屬性后,父組件的樣式將不會滲透到子組件中。
不過一個子組件的根節(jié)點會同時受其父組件的 scoped CSS 和子組件的 scoped CSS 的影響。這樣設(shè)計是為了讓父組件可以從布局的角度出發(fā),調(diào)整其子組件根元素的樣式。
如果你希望父組件的 scoped 樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,可以使用深度選擇器: >>> 操作符。
<style scoped>
.a >>> .b { /* ... */ }
</style>
上述代碼將會編譯成:
.a[data-v-f3f3eg9] .b { /* ... */ }
但是,有些像 Sass 之類的預(yù)處理器無法正確解析 >>>。這種情況下你可以使用 /deep/ 或 ::v-deep 操作符,這兩者都是 >>> 的別名,實現(xiàn)同樣的功能。
我們都知道,通過 v-html 創(chuàng)建的 DOM 內(nèi)容不受 scoped 樣式影響,可以通過深度作用選擇器>>>來為他們設(shè)置樣式。
七、路由的props屬性
一般在組件內(nèi)使用路由參數(shù),大多數(shù)人會這樣做:
export default {
methods: {
getParamsId() {
return this.$route.params.id
}
}
}
當(dāng)你隨便用用,臨時湊手,這沒什么問題,畢竟解決了需求。
可我們要隨時謹(jǐn)記:組件是用來復(fù)用的!組件應(yīng)該有高度的封閉性!
在組件中使用 $route 會使它與路由系統(tǒng)形成高度耦合,從而使組件只能在使用了路由功能的項目內(nèi),或某些特定的 URL 上使用,限制了其靈活性。
試想一下,如果你的組件被人拿去復(fù)用了,但是那個人并沒有使用路由系統(tǒng),而是通過別的方式傳遞id參數(shù),那么他該怎么辦?
正確的做法是通過 props 解耦!
首先,為組件定義一個叫做id的prop:
export default {
props: ['id'],
methods: {
getParamsId() {
return this.id
}
}
}
如果組件沒有對應(yīng)路由,那么這個id也可以通過父組件向子組件傳值的方式使用。
如果使用了路由,可以通過路由的prop屬性,傳遞id的值:
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: true
}]
})
將路由的 props 屬性設(shè)置為 true 后,組件內(nèi)可通過 props 接收到 params 參數(shù)
另外,你還可以通過函數(shù)模式來返回 props
const router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: (route) => ({
id: route.query.id
})
}]
})
其實,上面的技巧,在VueRouter的官檔都有說明。
八、異步組件
在大型應(yīng)用中,我們可能需要將應(yīng)用分割成小一些的代碼塊,并且只在需要的時候才從服務(wù)器加載一個模塊。
為了簡化,Vue 允許你以一個工廠函數(shù)的方式定義你的組件,這個工廠函數(shù)會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發(fā)該工廠函數(shù),且會把結(jié)果緩存起來供未來重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調(diào)傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
如你所見,這個工廠函數(shù)會收到一個 resolve 回調(diào),這個回調(diào)函數(shù)會在你從服務(wù)器得到組件定義的時候被調(diào)用。
你也可以調(diào)用 reject(reason) 來表示加載失敗。這里的 setTimeout 是為了演示用的,如何獲取組件取決于你自己。
一個推薦的做法是將異步組件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 這個特殊的 `require` 語法將會告訴 webpack
// 自動將你的構(gòu)建代碼切割成多個包,這些包
// 會通過 Ajax 請求加載
require(['./my-async-component'], resolve)
})
你也可以在工廠函數(shù)中返回一個 Promise,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:
Vue.component(
'async-webpack-example',
// 這個 `import` 函數(shù)會返回一個 `Promise` 對象。
() => import('./my-async-component')
)
當(dāng)使用局部注冊組件的時候,你也可以直接提供一個返回 Promise 的函數(shù):
new Vue({
// ...
components: {
'my-component': () => import('./my-async-component')
}
})
如果你想實現(xiàn)異步加載組件的功能,提高首屏顯示速度,那么可以使用上面例子中的定義組件的方法,也就是:箭頭函數(shù)+import語句!
處理加載狀態(tài)
2.3.0+ 新增
異步組件的工廠函數(shù)也可以返回一個如下格式的對象,用來靈活定制異步加載過程:
const AsyncComponent = () => ({
// 需要加載的組件 (應(yīng)該是一個 `Promise` 對象)
component: import('./MyComponent.vue'),
// 異步組件加載時使用的組件
loading: LoadingComponent,
// 加載失敗時使用的組件
error: ErrorComponent,
// 展示加載時組件的延時時間。默認(rèn)值是 200 (毫秒)
delay: 200,
// 如果提供了超時時間且組件加載也超時了,
// 則使用加載失敗時使用的組件。默認(rèn)值是:`Infinity`
timeout: 3000
})
注意如果你希望在 Vue Router 的路由組件中使用上述語法的話,必須使用 Vue Router 2.4.0+ 版本。
九、批量導(dǎo)入組件
很多時候我們會編寫一些類似輸入框或按鈕之類的基礎(chǔ)組件,它們是相對通用的組件,稱為基礎(chǔ)組件,它們會在更大一些的組件中被頻繁的用到。
這很容易導(dǎo)致大的組件里有一個很長的導(dǎo)入基礎(chǔ)組件的語句列表,例如:
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
//更多導(dǎo)入
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
當(dāng)你的基礎(chǔ)組件很多的時候,這個過程將非常重復(fù)、麻煩和無聊。
require.context()
如果你恰好使用了 webpack (或在內(nèi)部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 方法批量導(dǎo)入這些組件,然后將它們注冊為全局組件,這樣就可以在任何地方直接使用它們了,再也不用為導(dǎo)入的事情煩惱了!
下面是一個示例代碼:
import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其組件目錄的相對路徑
'./components',
// 是否查詢其子目錄
false,
// 匹配基礎(chǔ)組件文件名的正則表達(dá)式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 獲取組件的配置,也就是具體內(nèi)容,具體定義,組件的本身代碼
const componentConfig = requireComponent(fileName)
// 獲取組件的 PascalCase 命名,用來規(guī)范化組件名
const componentName = upperFirst(
camelCase(
// 獲取和目錄深度無關(guān)的文件名
fileName
.split('/')
.pop()
.replace(/\.\w+$/, '')
)
)
// 全局注冊組件
Vue.component(
componentName,
// 如果這個組件選項是通過 `export default` 導(dǎo)出的,
// 那么就會優(yōu)先使用 `.default`,
// 否則回退到使用模塊的根。
componentConfig.default || componentConfig
)
})
以上就是詳解vue高級特性的詳細(xì)內(nèi)容,更多關(guān)于vue高級特性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue.js響應(yīng)式數(shù)據(jù)的簡單實現(xiàn)方法(一看就會)
Vue最巧妙的特性之一是其響應(yīng)式系統(tǒng),下面這篇文章主要給大家介紹了關(guān)于Vue.js響應(yīng)式數(shù)據(jù)的簡單實現(xiàn)方法,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
Monaco?Editor開發(fā)SQL代碼提示編輯器實例詳解
這篇文章主要為大家介紹了Monaco?Editor開發(fā)SQL編輯器實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Vue 使用typescript如何優(yōu)雅的調(diào)用swagger API
這篇文章主要介紹了Vue 使用typescript如何優(yōu)雅的調(diào)用swagger API,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09

