歡迎您光臨本站 註冊首頁

詳解vue高級特性

←手機掃碼閱讀     madbeef @ 2020-06-11 , reply:0

Vue為我們提供了很多高級特性,學習和掌握它們有助於提高你的代碼水平。

一、watch進階

從我們剛開始學習Vue的時候,對於偵聽屬性,都是簡單地如下面一般使用:

  watch:{  	a(){  	 //doSomething  	}  }

 

實際上,Vue對watch提供了很多進階用法。

handler函數
 

以對象和handler函數的方式來定義一個監聽屬性,handler就是處理監聽變動時的函數:

  watch:{  	a:{  		handler:'doSomething'  	}  },  methods:{  	doSomething(){  		//當 a 發生變化的時候,做些處理  	}  }

 

handler有啥用?是多此一舉麼?用途主要有兩點:

1  將處理邏輯抽象出去了,以method的方式被複用

2  給定義下面兩個重要屬性留出了編寫位置

deep屬性

不知道你注意到了沒有?

當watch的是一個Object類型的數據,如果這個對象內部的某個值發生了改變,並不會觸發watch動作!

也就是說,watch默認情況下,不監測內部嵌套數據的變動。但是很多情況下,我們是需要監測的!

為解決這一問題,就要使用deep屬性:

  watch:{  	obj:{  		handler:'doSomething',  		deep:true  	}  },  methods:{  	doSomething(){  		//當 obj 發生變化的時候,做些處理  	}  }

 

deep屬性默認為false,也就是我們常用的watch模式。

immediate屬性

watch handler函數通常情況下只有在監聽的屬性發生改變時才會觸發。

但有些時候,我們希望在組件創建後,或者說watch被聲明和綁定的時候,立刻執行一次handler函數,這就需要使用immediate屬性了,它默認為false,改為true後,就會立刻執行handler。

  watch:{  	obj:{  		handler:'doSomething',  		deep:true,  		immediate:true  	}  },  methods:{  	doSomething(){  		//當 obj 發生變化的時候,做些處理  	}  }

 

同時執行多個方法
 

使用數組可以設置多項,形式包括字符串、函數、對象

   watch: {    // 你可以傳入回調數組,它們會被逐一調用    a: [           'handle1',           function handle2 (val, oldVal) { /* ... */ },           {      handler: function handle3 (val, oldVal) { /* ... */ },      /* ... */     }          ],       }

 

二、$event的不同表現

$event 是事件對象的特殊變量,在兩種場景下,它有不同的意義,代表不同的對象。

1  在原生事件中表示事件本身。可以通過$event.target獲得事件所在的DOM對象,再通過value進一步獲取具體的值。

  export default {    methods: {      inputHandler(msg, e) {        console.log(e.target.value)      }    }  }

 

2  而在父子組件通過自定義事件進行通信時,表示從子組件中傳遞出來的參數值

看下面的例子:

  //blog-post組件的模板Enlarge text

 

在父級組件監聽這個事件的時候,可以通過 $event 訪問到blog-post子組件傳遞出來的0.1這個值:

  

 

此時,$event的值就是0.1,而不是前面的事件對象。

三、異步更新隊列

1  Vue 在更新 DOM 時是異步執行的。

2  只要偵聽到數據變化,Vue 將開啟一個隊列,並緩衝在同一事件循環中發生的所有數據變更。

3  如果同一個 watcher 被多次觸發,只會被推入到隊列中一次。

這種在緩衝時去除重複數據對於避免不必要的計算和 DOM 操作是非常重要的。然後,在下一個的事件循環“tick”中,Vue 刷新隊列並執行實際 (已去重的) 工作。Vue 在內部對異步隊列嘗試使用原生的 Promise.thenMutationObserversetImmediate,如果執行環境不支持,則會採用 setTimeout(fn, 0) 代替。

例如,當你設置 vm.someData = 'new value',該組件不會立即重新渲染。當刷新隊列時,組件會在下一個事件循環“tick”中更新。

多數情況我們不需要關心這個過程,但是如果你想基於更新後的 DOM 狀態來做點什麼,這就可能會有些棘手。

雖然 Vue.js 通常鼓勵開發人員使用“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這麼做。為了在數據變化之後等待 Vue 完成更新 DOM,可以在數據變化之後立即使用 Vue.nextTick(callback)

這樣回調函數將在 DOM 更新完成後被調用。例如:

  {{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 實例上:

因為 $nextTick() 返回一個 Promise 對象,所以你可以使用新的 ES2017 async/await 語法完成相同的事情:

  methods: {   updateMessage: async function () {    this.message = '已更新'     //在這裡可以看出,message並沒有立刻被執行     //要理解頁面刷新和代碼執行速度的差別     //通常我們在頁面上立刻就能看到結果,那是因為一輪隊列執行其實很快,感覺不出DOM刷新的過程和所耗費的時間     //但對於代碼的執行,屬於即刻級別,DOM沒更新就是沒更新,就是會有問題    console.log(this.$el.textContent) // => '未更新'         await this.$nextTick()    console.log(this.$el.textContent) // => '已更新'   }  }

 

通俗的解釋:

1  Vue的DOM刷新機制是個異步隊列,並不是你想象中的立刻、馬上、即時更新!

2  這個異步隊列是一輪一輪的執行並刷新

3  上面帶來的問題是,一些依賴DOM更新完畢才能進行的操作(比如對新增加的DOM元素進行事件綁定),無法立刻執行,必須等待一輪隊列執行完畢

4  最容易碰到上面問題的地方:created生命週期鉤子函數中對DOM進行操作

5  解決辦法:使用this.nextTick(回調函數)方法,將對DOM的操作作為它的回調函數使用。

四、函數式組件
 

因為傳統編寫模板的能力不足,我們引入了渲染函數createElement。我們又希望獲得更多的靈活度,於是引入了JSX。最後,我們發現有些簡單的模板可以更簡單更小巧的實現,於是引入了函數式組件。Vue總是試圖為每一種場景提供不同的能力。

有這麼一類組件,它的特點是:

1  比較簡單

2  沒有管理任何狀態,也就是說無狀態,沒有響應式數據

3  沒有監聽任何傳遞給它的狀態

4  沒有寫生命週期方法

5  本質上只是一個接收一些prop的函數

6  沒有實例,沒有this上下文

那麼這個組件可以定義為函數式組件。與普通組件相比,函數式組件是無狀態的,無法實例化,沒有任何的生命週期和方法,適合只依賴於外部數據的變化而變化的組件,因其輕量,渲染性能會有所提高。

創建函數式組件

以定義全局組件的方式

  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。

應用場景

函數式組件的一個典型應用場景是作為包裝組件,比如當你碰到下面需求時:

程序化地在多個組件中選擇一個來代為渲染;

在將 childrenpropsdata 傳遞給子組件之前操作它們。

下面是一個 smart-list 組件的例子,它能根據傳入 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    )   }  })

 

五、監聽子組件的生命週期

假如我們有父組件Parent和子組件Child,如果在父組件中需要監聽子組件的mounted這個生命週期函數,並做一些邏輯處理,常規寫法可能如下:

  // Parent.vue//Child.vue  mounted(){    this.$emit('mounted');  }

 

但是,Vue給我們提供了一種更簡便的方法,子組件無需做任何處理,只需要在父組件引用子組件時使用@hook事件來監聽即可,代碼如下:

  // Parent.vuemethods:{    doSth(){      //some codes here    }  }

 

核心是@hook:mounted="doSth"的寫法!

當然這裡不僅僅可以監聽mounted,其他生命週期都可以監聽,例如created、updated等。

六、樣式穿透

我們知道,在單文件組件的style中使用 scoped 屬性後,父組件的樣式將不會滲透到子組件中。

不過一個子組件的根節點會同時受其父組件的 scoped CSS 和子組件的 scoped CSS 的影響。這樣設計是為了讓父組件可以從佈局的角度出發,調整其子組件根元素的樣式。

如果你希望父組件的 scoped 樣式中的一個選擇器能夠作用得“更深”,例如影響子組件,可以使用深度選擇器: >>> 操作符。

  >> .b { /* ... */ }" _ue_custom_node_="true">

 

上述代碼將會編譯成:

  .a[data-v-f3f3eg9] .b { /* ... */ }

 

但是,有些像 Sass 之類的預處理器無法正確解析 >>>。這種情況下你可以使用 /deep/ ::v-deep 操作符,這兩者都是 >>> 的別名,實現同樣的功能。

我們都知道,通過 v-html 創建的 DOM 內容不受 scoped 樣式影響,可以通過深度作用選擇器>>>來為他們設置樣式。

七、路由的props屬性

一般在組件內使用路由參數,大多數人會這樣做:

  export default {    methods: {      getParamsId() {        return this.$route.params.id      }    }  }

 

當你隨便用用,臨時湊手,這沒什麼問題,畢竟解決了需求。

可我們要隨時謹記:組件是用來複用的!組件應該有高度的封閉性!

在組件中使用 $route 會使它與路由系統形成高度耦合,從而使組件只能在使用了路由功能的項目內,或某些特定的 URL 上使用,限制了其靈活性。

試想一下,如果你的組件被人拿去複用了,但是那個人並沒有使用路由系統,而是通過別的方式傳遞id參數,那麼他該怎麼辦?

正確的做法是通過 props 解耦!

首先,為組件定義一個叫做id的prop:

  export default {    props: ['id'],    methods: {      getParamsId() {        return this.id      }    }  }

 

如果組件沒有對應路由,那麼這個id也可以通過父組件向子組件傳值的方式使用。

如果使用了路由,可以通過路由的prop屬性,傳遞id的值:

  const router = new VueRouter({    routes: [{      path: '/user/:id',      component: User,      props: true    }]  })

 

將路由的 props 屬性設置為 true 後,組件內可通過 props 接收到 params 參數

另外,你還可以通過函數模式來返回 props

  const router = new VueRouter({    routes: [{      path: '/user/:id',      component: User,      props: (route) => ({        id: route.query.id      })    }]  })

 

其實,上面的技巧,在VueRouter的官檔都有說明。

八、異步組件

在大型應用中,我們可能需要將應用分割成小一些的代碼塊,並且只在需要的時候才從服務器加載一個模塊。

為了簡化,Vue 允許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。例如:

  Vue.component('async-example', function (resolve, reject) {   setTimeout(function () {    // 向 `resolve` 回調傳遞組件定義    resolve({     template: 'I am async!'    })   }, 1000)  })

 

如你所見,這個工廠函數會收到一個 resolve 回調,這個回調函數會在你從服務器得到組件定義的時候被調用。

你也可以調用 reject(reason) 來表示加載失敗。這裡的 setTimeout 是為了演示用的,如何獲取組件取決於你自己。

一個推薦的做法是將異步組件和 webpack 的 code-splitting 功能一起配合使用:

  Vue.component('async-webpack-example', function (resolve) {   // 這個特殊的 `require` 語法將會告訴 webpack   // 自動將你的構建代碼切割成多個包,這些包   // 會通過 Ajax 請求加載   require(['./my-async-component'], resolve)  })

 

你也可以在工廠函數中返回一個 Promise,所以把 webpack 2 和 ES2015 語法加在一起,我們可以寫成這樣:

  Vue.component(   'async-webpack-example',   // 這個 `import` 函數會返回一個 `Promise` 對象。   () => import('./my-async-component')  )

 

當使用局部註冊組件的時候,你也可以直接提供一個返回 Promise 的函數:

  new Vue({   // ...   components: {    'my-component': () => import('./my-async-component')   }  })

 

如果你想實現異步加載組件的功能,提高首屏顯示速度,那麼可以使用上面例子中的定義組件的方法,也就是:箭頭函數+import語句

處理加載狀態
 

2.3.0+ 新增

異步組件的工廠函數也可以返回一個如下格式的對象,用來靈活定製異步加載過程:

  const AsyncComponent = () => ({   // 需要加載的組件 (應該是一個 `Promise` 對象)   component: import('./MyComponent.vue'),   // 異步組件加載時使用的組件   loading: LoadingComponent,   // 加載失敗時使用的組件   error: ErrorComponent,   // 展示加載時組件的延時時間。默認值是 200 (毫秒)   delay: 200,   // 如果提供了超時時間且組件加載也超時了,   // 則使用加載失敗時使用的組件。默認值是:`Infinity`   timeout: 3000  })

 

注意如果你希望在 Vue Router 的路由組件中使用上述語法的話,必須使用 Vue Router 2.4.0+ 版本。

九、批量導入組件
 

很多時候我們會編寫一些類似輸入框或按鈕之類的基礎組件,它們是相對通用的組件,稱為基礎組件,它們會在更大一些的組件中被頻繁的用到。

這很容易導致大的組件裡有一個很長的導入基礎組件的語句列表,例如:

  import BaseButton from './BaseButton.vue'  import BaseIcon from './BaseIcon.vue'  import BaseInput from './BaseInput.vue'  //更多導入    export default {   components: {    BaseButton,    BaseIcon,    BaseInput   }  }

 

當你的基礎組件很多的時候,這個過程將非常重複、麻煩和無聊。

require.context()

如果你恰好使用了 webpack (或在內部使用了 webpack 的 Vue CLI 3+),那麼就可以使用 require.context 方法批量導入這些組件,然後將它們註冊為全局組件,這樣就可以在任何地方直接使用它們了,再也不用為導入的事情煩惱了!

下面是一個示例代碼:

  import Vue from 'vue'  import upperFirst from 'lodash/upperFirst'  import camelCase from 'lodash/camelCase'    const requireComponent = require.context(   // 其組件目錄的相對路徑   './components',   // 是否查詢其子目錄   false,   // 匹配基礎組件文件名的正則表達式   /Base[A-Z]w+.(vue|js)$/  )    requireComponent.keys().forEach(fileName => {   // 獲取組件的配置,也就是具體內容,具體定義,組件的本身代碼   const componentConfig = requireComponent(fileName)     // 獲取組件的 PascalCase 命名,用來規範化組件名   const componentName = upperFirst(    camelCase(     // 獲取和目錄深度無關的文件名     fileName      .split('/')      .pop()      .replace(/.w+$/, '')    )   )     // 全局註冊組件   Vue.component(    componentName,    // 如果這個組件選項是通過 `export default` 導出的,    // 那麼就會優先使用 `.default`,    // 否則回退到使用模塊的根。    componentConfig.default || componentConfig   )  })

   


[madbeef ] 詳解vue高級特性已經有240次圍觀

http://coctec.com/docs/vue-js/show-post-238078.html