1. 程式人生 > >你應該知道的Vue高階特性

你應該知道的Vue高階特性

> 本文使用的Vue版本:2.6.10 Vue為我們提供了很多高階特性,學習和掌握它們有助於提高你的程式碼水平。 ## 一、watch進階 從我們剛開始學習Vue的時候,對於偵聽屬性,都是簡單地如下面一般使用: ```JS watch:{ a(){ //doSomething } } ``` 實際上,Vue對watch提供了很多進階用法。 ### handler函式 以物件和handler函式的方式來定義一個監聽屬性,handler就是處理監聽變動時的函式: ```JS watch:{ a:{ handler:'doSomething' } }, methods:{ doSomething(){ //當 a 發生變化的時候,做些處理 } } ``` handler有啥用?是多此一舉麼?用途主要有兩點: * 將處理邏輯抽象出去了,以method的方式被複用 * 給定義下面兩個重要屬性留出了編寫位置 ### deep屬性 不知道你注意到了沒有? **當watch的是一個Object型別的資料,如果這個物件內部的某個值發生了改變,並不會觸發watch動作!** 也就是說,watch預設情況下,不監測內部巢狀資料的變動。但是很多情況下,我們是需要監測的! 為解決這一問題,就要使用deep屬性: ```JS watch:{ obj:{ handler:'doSomething', deep:true } }, methods:{ doSomething(){ //當 obj 發生變化的時候,做些處理 } } ``` deep屬性預設為false,也就是我們常用的watch模式。 ### immediate屬性 `watch` 的`handler`函式通常情況下只有在監聽的屬性發生改變時才會觸發。 但有些時候,我們希望在元件建立後,或者說watch被宣告和繫結的時候,立刻執行一次handler函式,這就需要使用`immediate`屬性了,它預設為false,改為true後,就會立刻執行handler。 ```JS watch:{ obj:{ handler:'doSomething', deep:true, immediate:true } }, methods:{ doSomething(){ //當 obj 發生變化的時候,做些處理 } } ``` ### 同時執行多個方法 使用陣列可以設定多項,形式包括字串、函式、物件 ```JS watch: { // 你可以傳入回撥陣列,它們會被逐一呼叫 a: [ 'handle1', function handle2 (val, oldVal) { /* ... */ }, { handler: function handle3 (val, oldVal) { /* ... */ }, /* ... */ } ], } ``` ## 二、$event的不同表現 `$event` 是事件物件的特殊變數,在兩種場景下,它有不同的意義,代表不同的物件。 * 在原生事件中表示事件本身。可以通過`$event.target`獲得事件所在的DOM物件,再通過value進一步獲取具體的值。 ```JS export default { methods: { inputHandler(msg, e) { console.log(e.target.value) } } } ``` * 而在父子元件通過自定義事件進行通訊時,表示從子元件中傳遞出來的引數值 看下面的例子: ```JS //blog-post元件的模板 ``` 在父級元件監聽這個事件的時候,可以通過 `$event` 訪問到`blog-post`子元件傳遞出來的0.1這個值: ```JS ``` 此時,`$event`的值就是0.1,而不是前面的事件物件。 ## 三、非同步更新佇列 - Vue 在更新 DOM 時是**非同步**執行的。 - 只要偵聽到資料變化,Vue 將開啟一個佇列,並緩衝在同一事件迴圈中發生的所有資料變更。 - 如果同一個 watcher 被多次觸發,只會被推入到佇列中一次。 這種在緩衝時去除重複資料對於避免不必要的計算和 DOM 操作是非常重要的。然後,在下一個的事件迴圈“tick”中,Vue 重新整理佇列並執行實際 (已去重的) 工作。Vue 在內部對非同步佇列嘗試使用原生的 `Promise.then`、`MutationObserver` 和 `setImmediate`,如果執行環境不支援,則會採用 `setTimeout(fn, 0)` 代替。 例如,當你設定 `vm.someData = 'new value'`,該元件不會立即重新渲染。當重新整理佇列時,元件會在下一個事件迴圈“tick”中更新。 多數情況我們不需要關心這個過程,但是如果你想基於更新後的 DOM 狀態來做點什麼,這就可能會有些棘手。 雖然 Vue.js 通常鼓勵開發人員使用“資料驅動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這麼做。為了在資料變化之後等待 Vue 完成更新 DOM,可以在資料變化之後立即使用 `Vue.nextTick(callback)`。 這樣回撥函式將在 DOM 更新完成後被呼叫。例如: ```JS {{message}} var vm = new Vue({ el: '#example', data: { message: '123' } }) vm.message = 'new message' // 更改資料 vm.$el.textContent === 'new message' // false Vue.nextTick(function () { vm.$el.textContent === 'new message' // true }) ``` **在元件內使用 `vm.$nextTick()` 例項方法特別方便,因為它不需要全域性 `Vue`,並且回撥函式中的 `this` 將自動繫結到當前的 Vue 例項上:** ```JS Vue.component('example', { template: '{{ message }}', data: function () { return { message: '未更新' } }, methods: { updateMessage: function () { this.message = '已更新' console.log(this.$el.textContent) // => '未更新' this.$nextTick(function () { console.log(this.$el.textContent) // => '已更新' }) } } }) ``` 因為 `$nextTick()` 返回一個 `Promise` 物件,所以你可以使用新的 ES2017 `async/await` 語法完成相同的事情: ```JS methods: { updateMessage: async function () { this.message = '已更新' //在這裡可以看出,message並沒有立刻被執行 //要理解頁面重新整理和程式碼執行速度的差別 //通常我們在頁面上立刻就能看到結果,那是因為一輪佇列執行其實很快,感覺不出DOM重新整理的過程和所耗費的時間 //但對於程式碼的執行,屬於即刻級別,DOM沒更新就是沒更新,就是會有問題 console.log(this.$el.textContent) // => '未更新' await this.$nextTick() console.log(this.$el.textContent) // => '已更新' } } ``` **通俗的解釋**: * Vue的DOM重新整理機制是個非同步佇列,並不是你想象中的立刻、馬上、即時更新! * 這個非同步佇列是一輪一輪的執行並重新整理 * 上面帶來的問題是,一些依賴DOM更新完畢才能進行的操作(比如對新增加的DOM元素進行事件繫結),無法立刻執行,必須等待一輪佇列執行完畢 * 最容易碰到上面問題的地方:created生命週期鉤子函式中對DOM進行操作 * 解決辦法:使用`this.nextTick(回撥函式)`方法,將對DOM的操作作為它的回撥函式使用。 ## 四、函式式元件 因為傳統編寫模板的能力不足,我們引入了渲染函式createElement。我們又希望獲得更多的靈活度,於是引入了JSX。最後,我們發現有些簡單的模板可以更簡單更小巧的實現,於是引入了函式式元件。Vue總是試圖為每一種場景提供不同的能力。 有這麼一類元件,它的特點是: * 比較簡單 * 沒有管理任何狀態,也就是說無狀態,沒有響應式資料 * 沒有監聽任何傳遞給它的狀態 * 沒有寫生命週期方法 * 本質上只是一個接收一些prop的函式 * 沒有例項,沒有this上下文 那麼這個元件可以定義為函式式元件。與普通元件相比,函式式元件是無狀態的,無法例項化,沒有任何的生命週期和方法,適合只依賴於外部資料的變化而變化的元件,因其輕量,渲染效能會有所提高。 ### 建立函式式元件 * 以定義全域性元件的方式 ```JS Vue.component('my-component', { functional: true, // Props 是可選的 props: { // ... }, // 為了彌補缺少的例項 // 提供第二個引數作為上下文 render: function (createElement, context) { // ... } }) ``` 注意其中的`functional: true,` > 在 Vue 2.3.0 或以上的版本中,你可以省略 `props` 選項,所有元件上的 attribute 都會被自動隱式解析為 prop。 > > 當使用函式式元件時,該引用將會是 HTMLElement,因為他們是無狀態的也是無例項的。 * 對於單檔案元件,建立函式式元件的方式是在模板標籤內,新增`functional`屬性 ``` ``` ### 最重要的context引數 因為無狀態,沒有this上下文,所以函式式元件需要的一切都是通過 `context` 引數來傳遞,它是一個包括如下欄位的物件: - `props`:提供所有 prop 的物件 - `children`:VNode 子節點的陣列 - `slots`:一個函式,返回了包含所有插槽的物件 - `scopedSlots`:(2.6.0+) 一個暴露傳入的作用域插槽的物件。也以函式形式暴露普通插槽。 - `data`:傳遞給元件的整個資料物件,作為 `createElement` 的第二個引數傳入元件 - `parent`:對父元件的引用 - `listeners`:(2.3.0+) 一個包含了所有父元件為當前元件註冊的事件監聽器的物件。這是 `data.on` 的一個別名。 - `injections`:(2.3.0+) 如果使用了 `inject` 選項,則該物件包含了應當被注入的 property。 ### 應用場景 函式式元件的一個典型應用場景是作為包裝元件,比如當你碰到下面需求時: - 程式化地在多個元件中選擇一個來代為渲染; - 在將 `children`、`props`、`data` 傳遞給子元件之前操作它們。 下面是一個 `smart-list` 元件的例子,它能根據傳入 prop 的值來代為渲染更具體的元件: ```JS 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 ) } }) ``` ## 五、監聽子元件的生命週期 假如我們有父元件`Parent`和子元件`Child`,如果在父元件中需要監聽子元件的mounted這個生命週期函式,並做一些邏輯處理,常規寫法可能如下: ```kotlin // Parent.vue