Vue 常考基礎知識點萬字總結

對着 Vue 官網一行一行扒下來,知識點總結包含大部分 Vue 的基礎使用內容。javascript

數據與方法

當一個 Vue 實例被建立時,它將 data 對象中的全部的 property 加入到 Vue 的響應式系統中。當這些 property 的值發生改變時,視圖將會產生「響應」,即匹配更新爲新的值。當這些數據改變時,視圖會進行重渲染。html

須要注意: 只有當實例被建立時就已經存在於 data 中的 property 纔是響應式的。前端

也就是說若是你添加一個新的 property,那麼後續對於該 property 所做的改動將不會觸發任何視圖的更新。若是你知道你會在晚些時候須要一個 property,可是一開始它爲空或不存在,那麼你僅須要設置一些初始值。vue

當使用 Object.freeze() 對屬性進行凍結時,Vue 系統將再也不追蹤 property 的變化java

爲何 data 須要是函數?

組件複用時全部組件實例都會共享 data,若是 data 是對象的話,就會形成一個組件修改 data 之後會影響到其餘全部組件,因此須要將 data 寫成函數,每次用到就調用一次函數得到新的數據。node

當咱們使用 new Vue() 的方式的時候,不管咱們將 data 設置爲對象仍是函數都是能夠的,由於 new Vue() 的方式是生成一個根組件,該組件不會複用,也就不存在共享 data 的狀況了。jquery

模板

模板這塊沒有特別須要說明的,只有一處須要注意一下:webpack

有些 HTML 元素,諸如 <ul><ol><table><select>,對於哪些元素能夠出如今其內部是有嚴格限制的。而有些元素,諸如 <li><tr><option>,只能出如今其它某些特定的元素內部。程序員

這會致使咱們使用這些有約束條件的元素時遇到一些問題。例如:web

<table>
  <tr is="blog-post-row"></tr>
</table>
複製代碼

這個自定義組件 <blog-post-row> 會被做爲無效的內容提高到外部,並致使最終渲染結果出錯。幸虧這個特殊的 is attribute 給了咱們一個變通的辦法:

<table>
  <tr is="blog-post-row"></tr>
</table>
複製代碼

須要注意的是若是咱們從如下來源使用模板的話,這條限制是不存在

  • 字符串 (例如:template: '...')
  • 單文件組件 (.vue)
  • <script type="text/x-template">

生命週期

每一個 Vue 實例在被建立時都要通過一系列的初始化過程——例如,須要設置數據監聽、編譯模板、將實例掛載到 DOM 並在數據變化時更新 DOM 等。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數,這給了用戶在不一樣階段添加本身的代碼的機會。

Vue 的所有生命週期鉤子(非 SSR):

  • beforeCreate

    在實例初始化以後,數據觀測 (data observer) 和 event/watcher 事件配置以前被調用。

  • created

    在實例建立完成後被當即調用。在這一步,實例已完成如下的配置:數據觀測 (data observer),property 和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el property 目前尚不可用。

  • beforeMount

    在掛載開始以前被調用:相關的 render 函數首次被調用。

  • mounted

    實例被掛載後調用,這時 el 被新建立的 vm.$el 替換了。若是根實例掛載到了一個文檔內的元素上,當 mounted 被調用時 vm.$el 也在文檔內。

    注意: mounted 不會保證全部的子組件也都一塊兒被掛載。若是你但願等到整個視圖都渲染完畢,能夠在 mounted 內部使用 vm.$nextTick

  • beforeUpdate

    數據更新時調用,發生在虛擬 DOM 打補丁以前。這裏適合在更新以前訪問現有的 DOM,好比手動移除已添加的事件監聽器。

  • updated

    因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。

    當這個鉤子被調用時,組件 DOM 已經更新,因此你如今能夠執行依賴於 DOM 的操做。然而在大多數狀況下,你應該避免在此期間更改狀態。若是要相應狀態改變,一般最好使用計算屬性watcher 取而代之。

    注意: updated 不會保證全部的子組件也都一塊兒被重繪。若是你但願等到整個視圖都重繪完畢,能夠在 updated 裏使用 vm.$nextTick

  • activated

    被 keep-alive 緩存的組件激活時調用。

  • deactivated

    被 keep-alive 緩存的組件停用時調用。

  • beforeDestroy

    實例銷燬以前調用。在這一步,實例仍然徹底可用。

  • destroyed

    實例銷燬後調用。該鉤子被調用後,對應 Vue 實例的全部指令都被解綁,全部的事件監聽器被移除,全部的子實例也都被銷燬。

  • errorCaptured

    當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播。

須要注意: 若是使用 Vue 的服務端渲染框架好比:Nestjs,那麼生命週期中只有:beforeCreate、created 和 errorCaptured 有效。由於服務端渲染只負責第一屏,渲染完成後,後續的 DOM 掛載、數據變動、銷燬等,均由瀏覽器接管。

須要注意: 全部的生命週期鉤子自動綁定 this 上下文到實例中,所以你能夠訪問數據,對 property 和方法進行運算。這意味着你不能使用箭頭函數來定義一個生命週期方法 (例如 created: () => this.fetchTodos())。這是由於箭頭函數綁定了父上下文,所以 this 與你期待的 Vue 實例不一樣,this.fetchTodos 的行爲未定義。

下圖是一個 Vue 實例的生命週期圖譜:

關於Vue生命週期的總結

beforeCreate 鉤子函數調用的時候,是獲取不到 props 或者 data 中的數據的,由於這些數據的初始化都在 initState 中。

而後會執行 created 鉤子函數,在這一步的時候已經能夠訪問到以前不能訪問到的數據,可是這時候組件還沒被掛載,因此是看不到的。

接下來會先執行 beforeMount 鉤子函數,開始建立 VDOM,最後執行 mounted 鉤子,並將 VDOM 渲染爲真實 DOM 而且渲染數據。mounted 不會保證全部的子組件也都一塊兒被掛載。

接下來是數據更新時會調用的鉤子函數 beforeUpdateupdated,這兩個鉤子函數分別在數據更新前和更新後會調用。

另外還有 keep-alive 獨有的生命週期,分別爲 activateddeactivated 。用 keep-alive 包裹的組件在切換時不會進行銷燬,而是緩存到內存中並執行 deactivated 鉤子函數,命中緩存渲染後會執行 actived 鉤子函數。

最後就是銷燬組件的鉤子函數 beforeDestroydestroyed。前者適合移除事件、定時器等等,不然可能會引發內存泄露的問題。而後進行一系列的銷燬操做。

計算屬性和偵聽器

計算屬性的使用場景

當綁定在模板中的數據須要依賴其餘屬性值進行計算,或者須要通過一些複雜計算、特殊處理才能渲染到頁面上的時候,咱們可使用計算屬性:computed

計算屬性的特性

計算屬性可讓你的綁定值依賴其餘值的變化來進行動態計算,而且會根據依賴值進行緩存,只有當依賴值變化纔會返回新的計算內容。

計算屬性和偵聽器的區別

computed 是計算屬性,依賴其餘屬性計算值,而且 computed 的值有緩存,只有當計算值變化纔會返回內容。watch 監聽到值的變化就會執行回調,在回調中能夠進行一些邏輯操做。

因此通常來講須要依賴別的屬性來動態得到值的時候可使用 computed,對於監聽到值的變化須要作一些複雜業務邏輯的狀況可使用 watch

計算屬性和偵聽器還支持對象形式的寫法:

// watch
vm.$watch('obj', {
    // 深度遍歷
    deep: true,
    // 當即觸發
    immediate: true,
    // 執行的函數
    handler: function(val, oldVal) {}
})

// computed
var vm = new Vue({
  data: { a: 1 },
  computed: {
    aPlus: {
      // this.aPlus 時觸發
      get: function () {
        return this.a + 1
      },
      // this.aPlus = 1 時觸發
      set: function (v) {
        this.a = v - 1
      }
    }
  }
});
複製代碼

設置偵聽器 deeptrue,則能開啓對於對象屬性的深度監聽模式,也就是說,對象下的子屬性發生變更,偵聽器同樣可以監測到變化而且觸發。

計算屬性和方法的區別

有的時候,計算屬性和方法(methods)均可以達到相同的效果,那麼它們之間的區別是什麼呢?

計算屬性是基於它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們纔會從新求值。這就意味着只要依賴值尚未發生改變,屢次訪問計算屬性會當即返回以前的計算結果,而沒必要再次執行函數。而 相比之下,每當觸發從新渲染時,調用方法將總會再次執行函數,這是有性能損耗的。

Class 與 Style 綁定

自動添加前綴

v-bind:style 使用須要添加瀏覽器引擎前綴的 CSS property 時,如 transform,Vue.js 會自動偵測並添加相應的前綴。

條件渲染

v-show 與 v-if 區別

v-show 只是在 display: nonedisplay: block 之間切換。不管初始條件是什麼都會被渲染出來,後面只須要切換 CSS,DOM 仍是一直保留着的。因此總的來講 v-show 在初始渲染時有更高的開銷,可是切換開銷很小,更適合於頻繁切換的場景。

v-if 的話就得說到 Vue 底層的編譯了。當屬性初始爲 false 時,組件就不會被渲染,直到條件爲 true,而且切換條件時會觸發銷燬/掛載組件,因此總的來講在切換時開銷更高,更適合不常常切換的場景。

而且基於 v-if 的這種惰性渲染機制,能夠在必要的時候纔去渲染組件,減小整個頁面的初始渲染開銷。

key 管理可複用的元素

Vuekey 這個東西官網上說的官方解釋有些抽象,推薦你們看:Vue2.0 v-for 中 :key 到底有什麼用?

簡單來講:

  • 若是這個元素沒有加上 key,那麼在 Vue 下一次更新視圖的時候,它就會盡量地將該元素進行復用,而不是從新創造一個新的元素將其替換。典型的場景是:表單登陸,用戶能夠選擇微信號或者手機號登陸,當用戶輸入帳號後突然想切換登陸方式,代碼裏雖然是使用的 v-if 進行了微信號和手機號兩個 input 之間的切換,可是以前填寫的登陸帳號信息依然會保留下來,這個就是 Vue 對 Input 標籤進行了複用。
  • 若是這個元素上加了惟一的 key ,就等於說給這個元素及其子元素 / 子組件加上了一個惟一的 id 標識,那麼 Vue 在下一次更新視圖的時候,就可以識別出哪些元素是被修改了,哪些元素是被刪除掉了,從而更有針對性地選擇複用仍是重建元素,而不是直接一股腦地能複用就複用。

官網解釋:

key 的特殊 attribute 主要用在 Vue 的虛擬 DOM 算法,在新舊 nodes 對比時辨識 VNodes。若是不使用 key,Vue 會使用一種 最大限度減小動態元素 而且 儘量的嘗試就地修改/複用相同類型元素 的算法。而使用 key 時,它會基於 key 的變化從新排列元素順序,而且會移除 key 不存在的元素

有相同父元素的子元素必須有獨特的 key。重複的 key 會形成渲染錯誤。

最多見的用例是結合 v-for

<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>
複製代碼

它也能夠用於強制替換元素/組件而不是重複使用它。當你遇到以下場景時它可能會頗有用:

  • 完整地觸發組件的生命週期鉤子
  • 觸發過渡

例如:

<transition>
  <span :key="text">{{ text }}</span>
</transition>
複製代碼

text 發生改變時,<span> 老是會被替換而不是被修改,所以會觸發過渡。

列表渲染

這裏的 v-for 指令就涉及到上面所說的 key 的問題。

假設有一個列表,列表有三個子組件,自組件沒有加惟一 key 值,每一個子組件裏面有一個「有狀態的」孫子組件。

如今用戶點擊刪除按鈕,刪除掉第二項,會出現如下結果:Vue 識別自組件 2 的名稱被修改成 3 可是其餘的東西都會進行復用,包括自組件,而後刪除原來的 3 組件及其自組件。這就出現了問題。

不要使用 index 索引做爲組件的 key!由於,這樣的話,當你刪除了一項好比第二項,那麼第三項組件的 key 就變成了 2 ,Vue 在進行 VNodes 比對的時候,還會認爲是你只是將組件 2 的名稱修改爲了組件 3,而後仍是會複用,因而仍是會出現上面的問題。

數組的更新監測

Vue 將被偵聽的數組的變動方法進行了包裹,因此它們也將會觸發視圖更新。這些被包裹過的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

因爲 JavaScript 的限制,Vue 不能檢測 數組和對象的變化。好比,若是你使用 arr.newItem = " "; 或者 obj.newKey = " "; 這種方式爲數組 / 對象新增元素,這些新增的元素 Vue 是監聽不到的。

不要將 v-forv-if 一同使用

永遠不要把 v-ifv-for 同時用在同一個元素上。

當 Vue 處理指令時,v-forv-if 具備更高的優先級,因此這個模板:

<ul>
  <li v-for="user in users" v-if="user.isActive" :key="user.id" >
    {{ user.name }}
  </li>
</ul>
複製代碼

將會通過以下運算:

this.users.map(function (user) {
  if (user.isActive) {
    return user.name
  }
})
複製代碼

所以哪怕咱們只渲染出一小部分用戶的元素,也得在每次重渲染的時候遍歷整個列表,不論活躍用戶是否發生了變化。

經過將其更換爲在以下的一個計算屬性上遍歷:

computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
  }
}
複製代碼

通常咱們在兩種常見的狀況下會傾向於這樣作:

  • 爲了過濾一個列表中的項目 (好比 v-for="user in users" v-if="user.isActive")。在這種情形下,請將 users 替換爲一個計算屬性 (好比 activeUsers),讓其返回過濾後的列表。
  • 爲了不渲染本應該被隱藏的列表 (好比 v-for="user in users" v-if="shouldShowUsers")。這種情形下,請將 v-if 移動至容器元素上 (好比 ulol)。

因此,永遠不要這麼寫:

<ul>
 <li v-for="user in users" v-if="user.isActive" :key="user.id" >
   {{ user.name }}
 </li>
</ul>
複製代碼
<ul>
 <li v-for="user in users" v-if="shouldShowUsers" :key="user.id" >
   {{ user.name }}
 </li>
</ul>
複製代碼

你能夠這麼寫:

<ul>
<!-- activeUsers 是計算屬性過濾後的有效用戶列表 -->
 <li v-for="user in activeUsers" :key="user.id" >
   {{ user.name }}
 </li>
</ul>
複製代碼
<ul v-if="shouldShowUsers">
 <li v-for="user in users" :key="user.id" >
   {{ user.name }}
 </li>
</ul>
複製代碼

事件

事件處理這塊多是 Vuer 用的最多的東西之一,大部份內容你們可能也瞭解,就沒什麼好說的了,就大概總結一下吧。

事件的寫法

在 Vue 中有下面幾種事件的寫法:

  • 內聯寫法:

    <div id="example-1">
      <button v-on:click="counter += 1">Add 1</button>
      <p>The button above has been clicked {{ counter }} times.</p>
    </div>
    複製代碼
  • 方法名寫法,這種寫法,默認會傳入一個 DOM 的原生事件對象做爲事件處理函數的第一個參數

    <div id="example-2">
      <!-- `greet` 是在下面定義的方法名 -->
      <button v-on:click="greet">Greet</button>
    </div>
    複製代碼
    // 在 `methods` 對象中定義方法
      methods: {
        greet: function (event) {
          // `this` 在方法裏指向當前 Vue 實例
          alert('Hello ' + this.name + '!')
          // `event` 是原生 DOM 事件
          if (event) {
            alert(event.target.tagName)
          }
        }
      }
    複製代碼
  • 內聯語句中直接調用:

    <div id="example-3">
      <button v-on:click="say('hi')">Say hi</button>
      <button v-on:click="say('what')">Say what</button>
    </div>
    複製代碼

    有時也須要在內聯語句處理器中訪問原始的 DOM 事件。能夠用特殊變量 $event 把它傳入方法:

    <button v-on:click="warn('Form cannot be submitted yet.', $event)">
      Submit
    </button>
    複製代碼

注意:不一樣於組件和 prop,事件名不存在任何自動化的大小寫轉換。而是觸發的事件名須要徹底匹配監聽這個事件所用的名稱。舉個例子,若是觸發一個 camelCase 名字的事件:

this.$emit('myEvent');
複製代碼

則監聽這個名字的 kebab-case 版本是不會有任何效果的:

<!-- 沒有效果 -->
<my-component v-on:my-event="doSomething"></my-component>
複製代碼

v-on:myEvent 將會變成 v-on:myevent——致使 myEvent 不可能被監聽到。

所以,推薦 始終使用 kebab-case 的事件名

事件修飾符

這塊不是面試的重點,你們大概看一下文檔瞭解就好:事件處理——事件修飾符。只有一種須要特殊說明一下:.sync 修飾符

.sync 修飾符也是語法糖的一種,在有些狀況下,咱們可能須要對一個 prop 進行「雙向綁定」。不幸的是,真正的雙向綁定會帶來維護上的問題,由於子組件能夠變動父組件,且在父組件和子組件都沒有明顯的變動來源。這個時候,咱們可使用 .sync 語法糖來模擬雙向綁定,而且 .sync 也可以在語意上讓子組件更改父組件的狀態代碼更容易被區分。

<comp :foo.sync="bar"></comp>
複製代碼

等價於:

<comp :foo="bar" @update:foo="val => bar = val"></comp>
複製代碼

當子組件須要更新 foo 的值時,它須要顯式地觸發一個更新事件:

this.$emit('update:foo', newValue);
複製代碼

.sync 修飾符常常會用在彈框的顯隱功能上:

<!-- 父組件中 -->
<Modal :show.sync="shoModal" />
複製代碼
<!-- 子組件中 -->
<div class="mask" v-show="show">
    <span class="close" @click="close">X</span>
</div>
複製代碼
// 子組件 vue 文件
{
    props: ['show'],
    methods: {
        close() {
            this.$emit('update:show', false);
        }
    }
}

複製代碼

雙向綁定 v-model

v-model 的本質——語法糖

你能夠用 v-model 指令在表單 <input><textarea><select> 元素上建立雙向數據綁定。其實,v-model 本質上只是 v-bind:valuev-on:input 的語法糖而已:

使用 v-model

<input v-model="val" />
複製代碼

等價於:

<input :value="val" @input="evt => val = evt.target.value" />
複製代碼

v-model 語法糖在內部爲不一樣的輸入元素使用不一樣的 property 並拋出不一樣的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段將 value 做爲 prop 並將 change 做爲事件。

v-model 修飾符

這塊內容面試不太可能問到,不過做爲平時開發的小技巧能夠學習一下。

.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 輸入元素的值也總會返回字符串。若是這個值沒法被 parseFloat() 解析,則會返回原始的值。

.trim

若是要自動過濾用戶輸入的首尾空白字符,能夠給 v-model 添加 trim 修飾符:

<input v-model.trim="msg">
複製代碼

自定義組件使用 v-model

自定義組件使用 v-model 的方式,和原生 DOM 上綁定的 v-model 方式同樣,只須要組件內部對外暴露 value prop ,而且組件內部向外發射 input 事件便可。

**注意:**一個組件上的 v-model 默認會利用名爲 value 的 prop 和名爲 input 的事件,可是像單選框、複選框等類型的輸入控件可能會將 value attribute 用於不一樣的目的。

<custom-input v-model="searchText"></custom-input>
複製代碼

等價於

<custom-input v-bind:value="searchText" v-on:input="searchText = $event" ></custom-input>
複製代碼

組件內部

Vue.component('custom-input', {
  props: ['value'],
  template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > `
})
複製代碼

自定義組件

組件是可複用的 Vue 實例,因此它們與 new Vue 接收相同的選項,例如 datacomputedwatchmethods 以及生命週期鉤子等。僅有的例外是像 el 這樣根實例特有的選項。

全局註冊

直接使用 Vue.component 進行全局註冊,而後再建立 Vue 的根實例,須要注意的是:全局註冊必須在根 Vue 實例建立以前。

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
複製代碼

局部註冊

局部註冊沒有什麼好說的,你們基本都明白。

單個根元素

每一個組件必須只有惟一一個根元素

Props

靜態 Props 和動態 Props

靜態 props

<my-component title="This is title" />
複製代碼

動態 props

<my-component v-bind:title="title" />
複製代碼

多個 Props 的傳入方式

咱們能夠對一個組件傳入多個 props,若是組件的 props 太多,能夠將 props 組合成一個對象 prop 傳入。

<my-component v-bind:title="title" v-bind:name="name" v-bind:age="age" />

<!-- 統一放入一個 prop 對象中傳入 -->
<my-component v-bind:obj="{title: 'title', name: 'name', age: 'age'}" />

複製代碼

也能夠傳入一個對象的全部 property:

若是你想要將一個對象的全部 property 都做爲 prop 傳入,你可使用不帶參數的 v-bind (取代 v-bind:prop-name)。例如,對於一個給定的對象 post

post: {
  id: 1,
  title: 'My Journey with Vue'
}
複製代碼

下面的模板:

<blog-post v-bind="post"></blog-post>
複製代碼

等價於:

<blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post>
複製代碼

Props 的接收方式

組件內部接收 props 時咱們能夠寫成數組形式:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
複製代碼

也能夠寫成對象形式,若是寫成對象形式的話,就須要 Props 的類型判斷:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}
複製代碼

props 的類型檢查

type 能夠是下列原生構造函數中的一個:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
Vue.component('my-component', {
  props: {
    // 基礎的類型檢查 (`null` 和 `undefined` 會經過任何類型驗證)
    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 ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }
})
複製代碼

額外的,type 還能夠是一個自定義的構造函數,而且經過 instanceof 來進行檢查確認。例如,給定下列現成的構造函數:

function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}
複製代碼

你可使用:

Vue.component('blog-post', {
  props: {
    author: Person
  }
})
複製代碼

來驗證 author prop 的值是不是經過 new Person 建立的。

Props 和 html-attribute

注意:Vue 的組件中是不限制 prop 的傳入數量的,若是外部傳入的 prop ,組件內部沒有接收,那麼就會變成 kebab-case (短橫線分隔命名) 命名 的方式做爲自定義屬性添加到組件的根元素上。

Vue 會選擇性地對重複的 attribute 進行替換或者合併,好比,若是是重複的 input type ,那麼則會替換,若是是 class、style 等,則會合並。對於絕大多數 attribute 來講,從外部提供給組件的值會替換掉組件內部設置好的值, classstyle attribute 會稍微智能一些,即兩邊的值會被合併起來,從而獲得最終的值。

若是你但願組件的根元素繼承 attribute,你能夠在組件的選項中設置 inheritAttrs: false。例如:

Vue.component('my-component', {
  inheritAttrs: false,
  // ...
})
複製代碼

注意:inheritAttrs: false 選項 不會 影響 styleclass 的綁定。

vm.$attrs

inheritAttrs: false 的狀況很適合配合 vm.$attrs 來使用。vm.$attrs 包含了父做用域中不做爲 prop 被識別 (且獲取) 的 attribute 綁定 (classstyle 除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (classstyle 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件,這在建立高級別的組件時很是有用。有了 inheritAttrs: false$attrs,你就能夠手動決定這些 attribute 會被賦予哪一個元素。這個相似於 React 中的 <MyComponent {...this.props} />

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label> `
})
複製代碼

組件通信

組件通訊通常分爲如下幾種狀況:

  • 父子組件通訊
  • 兄弟組件通訊
  • 跨多層級組件通訊
  • 任意組件

對於以上每種狀況都有多種方式去實現,接下來就來學習下如何實現。

父子通訊

props 和 $emit

父組件經過 props 傳遞數據給子組件,子組件經過 emit 發送事件傳遞數據給父組件,這兩種方式是最經常使用的父子通訊實現辦法。

這種父子通訊方式也就是典型的單向數據流,父組件經過 props 傳遞數據,子組件不能直接修改 props, 而是必須經過發送事件的方式告知父組件修改數據。

v-model 和 .sync

另外這兩種方式還可使用語法糖 v-model 來直接實現,由於 v-model 默認會解析成名爲 valueprop 和名爲 input 的事件。這種語法糖的方式是典型的雙向綁定,經常使用於 UI 控件上,可是究其根本,仍是經過事件的方法讓父組件修改數據。

.sync 屬性是個語法糖,能夠很簡單的實現子組件與父組件通訊

<!--父組件中-->
<input :value.sync="value" />
<!--以上寫法等同於-->
<input :value="value" @update:value="v => value = v"></comp>
<!--子組件中-->
<script> this.$emit('update:value', 1) </script>
複製代碼

p a r e n t parent 和 children 和 $ref

固然咱們還能夠經過訪問 $parent 或者 $children 對象來訪問組件實例中的方法和數據,或者使用 this.$refs[compnentRefName] 的方式來拿到具名子元素實例。

另外若是你使用 Vue 2.3 及以上版本的話還可使用 $listeners.sync 這兩個屬性。

注意:$refs 只會在組件渲染完成以後生效,而且它們不是響應式的。這僅做爲一個用於直接操做子組件的「逃生艙」——你應該避免在模板或計算屬性中訪問 $refs

$listeners

$listeners 屬性會將父組件中的 (不含 .native 修飾器的) v-on 事件監聽器傳遞給子組件,子組件能夠經過訪問 $listeners 來自定義監聽器。

vm.$listener

簡單點講它是一個對象,裏面包含了做用在這個組件上全部的監聽器(監聽事件),能夠經過 v-on="$listeners" 將事件監聽指向這個組件內的子元素(包括內部的子組件)。

<div id="app">
    <child1 :p-child1="child1" :p-child2="child2" :p-child-attrs="1231" v-on:test1="onTest1" v-on:test2="onTest2">
    </child1>
  </div>
複製代碼
Vue.component("Child1", {
        inheritAttrs: true,
        props: ["pChild1"],
        template: ` <div class="child-1"> <p>in child1:</p> <p>props: {{pChild1}}</p> <p>$attrs: {{this.$attrs}}</p> <hr> <child2 v-bind="$attrs" v-on="$listeners"></child2></div>`,
        mounted: function() {
          this.$emit("test1");
        }
      });
      Vue.component("Child2", {
        inheritAttrs: true,
        props: ["pChild2"],
        template: ` <div class="child-2"> <p>in child->child2:</p> <p>props: {{pChild2}}</p> <p>$attrs: {{this.$attrs}}</p> <button @click="$emit('test2','按鈕點擊')">觸發事件</button> <hr> </div>`,
        mounted: function() {
          this.$emit("test2");
        }
      });
      const app = new Vue({
        el: "#app",
        data: {
          child1: "pChild1的值",
          child2: "pChild2的值"
        },
        methods: {
          onTest1() {
            console.log("test1 running...");
          },
         onTest2(value) {
            console.log("test2 running..." + value);
          }
        }
      });
複製代碼

上例中,父組件在child1組件中設置兩個監聽事件test1test2,分別在child1組件和child1組件內部的child2組件中執行。還設置了三個屬性p-child1p-child2p-child-attrs。其中p-child1p-child2被對應的組件的prop識別。因此:
p-child1組件中$attrs{ "p-child2": "pChild2的值", "p-child-attrs": 1231 };
p-child2組件中$attrs{ "p-child-attrs": 1231 },效果以下圖:

咱們再點擊child2組件中的按鈕,觸發事件,控制檯能夠打印出:

兄弟組件通訊

對於這種狀況能夠經過查找父組件中的子組件實現,也就是 this.$parent.$children,在 $children 中能夠經過組件 name 查詢到須要的組件實例,而後進行通訊。

跨多層次組件通訊

provide / inject

對於這種狀況可使用 Vue 2.2 新增的 API provide / inject,雖然文檔中不推薦直接使用在業務中,可是若是用得好的話仍是頗有用的。

provide 選項容許咱們指定咱們想要 提供 給後代組件的數據 / 方法,而後在後代組件中使用 inject 注入,就能夠訪問組件組件提供的 provide 出來的數據或者調用 provide 出來的方法。
假設有父組件 A,而後有一個跨多層級的子組件 B:

// 父組件 A
export default {
  provide: {
    data: 1,
    getMap: this.getMap // getMap 是個方法
  }
}
// 子組件 B
export default {
  inject: ['data', 'getMap'],
  mounted() {
    // 不管跨幾層都能得到祖先組件的 data 屬性
    console.log(this.data) // => 1
    // 不管跨幾層都能調用祖先組件的 getMap 方法
    this.getMap() // => 1
  }
}
複製代碼

任意組件

這種方式能夠經過 Vuex 或者 Event Bus 解決,另外若是你不怕麻煩的話,可使用這種方式解決上述全部的通訊狀況

Event Bus

Event Bus 的通訊機制其實很是簡單:獨立與主 Vue 實例再建立一個單獨的 Vue 實例,利用 vm.$on vm.$off vm.$once 等事件監聽 / 解綁的 api ,專門就作事件處理的事情。由於他式獨立的 Vue 實例,因此並不須要知道誰監聽了事件,誰觸發了事件。很是的方便。

動態組件

有的時候,在不一樣組件之間進行動態切換是很是有用的,好比在一個多標籤的界面裏。上述內容能夠經過 Vue 的 <component> 元素加一個特殊的 is attribute 來實現,也就是動態組件:

<!-- 組件會在 `currentTabComponent` 改變時改變 -->
<component v-bind:is="currentTabComponent"></component>
複製代碼

在上述示例中,currentTabComponent 能夠包括

  • 已註冊組件的名字,或
  • 一個組件的選項對象

動態組件也可使用 keep-alive 進行緩存

異步組件

在大型應用中,咱們可能須要將應用分割成小一些的代碼塊,而且只在須要的時候才從服務器加載一個模塊,這個就是異步組件加載。

異步組件的全局註冊

<template>
<div> <!-- 異步組件測試 點擊按鈕後 第一個延遲300毫秒,從服務器加載 第二個不延遲從服務器加載 --> <template v-if="show"> <later></later> <later2></later2> </template> <button @click="toggle">加載</button> </div>
</template>
<script> import Vue from 'vue'; const later = Vue.component('later', function (resolve) { setTimeout(function () { require(['./later.vue'], resolve) }, 3000); }); const later2 = Vue.component('later2', function (resolve) { require(['./later2.vue'], resolve) }); export default{ data: function () { return { show: false }; }, components: { later, later2, }, created: function () { }, mounted: function () { }, computed: {}, methods: { toggle:function () { this.show = !this.show; } }, } </script>
<style> </style>
複製代碼

異步組件的局部註冊

異步組建的局部註冊惟一的區別就是 components 的寫法

components: {
   AddCustomerSchedule(resolve) {
     require(["../components/AddCustomer"], resolve);
   },
   AddPeopleSchedule(resolve) {
     require(["../components/AddPeople"], resolve);
   },
   CustomerInfoSchedule(resolve) {
     require(["../components/CustomerInfo"], resolve);
   },
   VisitRecordSchedule(resolve) {
     require(["../components/VisitRecord"], resolve);
   },
},
複製代碼

遞歸組件

有的時候,原本想秀下操做,然而因爲 水平菜雞 業務複雜,稍有不慎咱們就很容易寫出死循環的遞歸組件:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
複製代碼

相似上述的組件將會致使「max stack size exceeded」錯誤,因此請確保遞歸調用是條件性的 (例如使用一個最終會獲得 falsev-if)。

組件的循環引用

組件間的循環引入的場景之一就是—— tree 組件。致使死循環引用的緣由,實際上是由於兩個 vue 文件中的循環 import,webpack 打包這種文件的時候,就會不知所措,因而只能給你拋出一個錯誤。因此這種場景每每出如今局部註冊的組件下,而 Vue.component() 進行的全局組件註冊因爲不存在循環引用,因此沒有這個問題。

對於這個問題的解決方案有兩種:

  1. 你能夠在 beforeCreate 鉤子函數裏進行引入:

    beforeCreate: function () {
      this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
    }
    複製代碼
  2. 你能夠在註冊局部組件的時候,使用動態組件的註冊方式:

    components: {
      TreeFolderContents: () => import('./tree-folder-contents.vue')
    }
    複製代碼

兩種解決問題的核心思想,都是延遲引入,從而規避掉 webpack 打包時可能出現的懵逼狀況。

keep-alive

keep-alive 多出來兩個鉤子函數 activateddeactivated 。用 keep-alive 包裹的組件在切換時不會進行銷燬,而是緩存到內存中並執行 deactivated 鉤子函數,命中緩存渲染後會執行 actived 鉤子函數。

對於這個知識點,通常也就問問它是作什麼用的,也不太會問別的東西,畢竟沒有什麼值得深挖的。只要能答上來這個是作組件緩存的基本就沒問題。想要詳細瞭解的,這裏給個傳送門吧:keep-alive

分發插槽

當咱們須要在自定義組件中再嵌套自定義組件,就會用到 「分發插槽」 的功能,這個就相似於 React 中的語法 <MyComponent>{...this.children}</MyComponent>

默認插槽

Vue 組件支持一個默認默認插槽,默認插槽有得話只能有一個:

<!--父組件-->
<my-comp>
    Hello world!
</my-comp>

<!--MyComp 組件-->
<div>
    <slot><slot>
</div>

<!--最終渲染結果-->
<div>
    Hello world!
</div>
複製代碼

你也能夠爲插槽提供一個後備用內容,當父組件沒有任何東西插入的時候,後備內容就會默認顯示,像這樣:

<!--MyComp 組件-->
<div>
    <slot>Hi World!<slot>
</div>
複製代碼

具名插槽

具名插槽能夠有不少,不過名稱不能重複,具名插槽和默認插槽能夠同時存在:

<!--父組件-->
<my-comp>
    <template v-slot:header>This is header</template>
    <template v-slot:body>This is body</template>
    Hello World!
</my-comp>

<!--MyComp 組件-->
<div>
    <slot name="header"><slot>
    <slot name="body"><slot>
    <slot><slot>
</div>

<!--最終渲染結果-->
<div>
    This is header
    This is body
    Hello world!
</div>
複製代碼

做用域插槽

當使用插槽的時候,父級模板裏的全部內容都是在父級做用域中編譯的;子模板裏的全部內容都是在子做用域中編譯的。也就是說,子元素沒有辦法訪問到父模板中的內容,那麼這個時候可使用做用域插槽來將父模板裏的內容傳入:

<!--父組件-->
<my-comp>
    <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
    </template>
</my-comp>

<!--MyComp 組件-->
<div>
    <slot v-bind:user="user">
        {{ user.lastName }}
    <slot>
</div>
複製代碼

若是是默認插槽的 prop 可使用簡寫:

<!--父組件-->
<current-user>
    <template v-slot="slotProps">
        {{ slotProps.user.firstName }}
    </template>
</current-user>

<!--MyComp 組件-->
<div>
    <slot v-bind:user="user">
        {{ user.lastName }}
    <slot>
</div>
複製代碼

注意: 默認插槽的縮寫語法 不能 和具名插槽混用,由於它會致使做用域不明確

<!--父組件-->
<!-- 無效,會致使警告 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</current-user>
複製代碼

只要出現多個插槽,請始終爲_全部的_插槽使用完整的基於 <template> 的語法

<!--父組件-->
<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>
複製代碼

能夠以解構的方式寫插槽

<!--父組件-->
<current-user>
  <template v-slot:default="{user}">
    {{ user.firstName }}
  </template>
</current-user>
複製代碼

動態插槽

動態指令參數也能夠用在 v-slot 上,來定義動態的插槽名

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>
複製代碼

具名插槽縮寫

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>
複製代碼

加入做用域插槽

<current-user #default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>
複製代碼

混入

咱們寫一些類似度很高的組件時,常常會發現不少可以提取出來公共使用的東西部分,Vue 對於這部份內容,提供了一個混入的工具:mixin。mixin 對象,其實也就是普通的 vue 對象而已。

全局混入 Mixin

Vue.mixin({
    beforeCreate() {
        // ...邏輯
        // 這種方式會影響到每一個組件的 beforeCreate 鉤子函數
    }
})
複製代碼

局部混入 Mixins

const mixinObj = {
    methods: {
        mixinFunc() {}
    }
}

new Vue({
    mixins: [mixinObj],
    created() {
    this.mixinFunc();
    }
});
複製代碼

使用 mixin 混入時:

  • mixin 的鉤子函數會再本身組件的鉤子函數以前觸發
  • 對於 methods、computed 這些配置項等,Vue 會進行合併
  • 對於同名屬性,Vue 會用組件裏的來覆蓋 mixin 的

自定義混入方式

這個我歷來沒有用過,也沒有被問到過,感受不重要也不經常使用,須要的話自取文檔:自定義選項合併策略

混入和 extend

Vue 裏還有一個 extend 的 api,那麼這個和 mixin 有什麼區別呢?我的感受這兩個東西的目的都是同樣的:都是做爲混入和擴展的一種方法,只不過實現層面不一樣:mixin 是在組件層面混入,或者能夠理解爲對實例進行擴展;而 extend 是先擴展了構造函數也就是累類,而後根據這個擴展完成的類,在再進行實例化。看代碼:

// 建立組件構造器
let Component = Vue.extend({
  template: '<div>test</div>'
})
// 掛載到 #app 上
new Component().$mount('#app')
// 除了上面的方式,還能夠用來擴展已有的組件
let SuperComponent = Vue.extend(Component)
new SuperComponent({
    created() {
        console.log(1)
    }
})
new SuperComponent().$mount('#app')
複製代碼

自定義指令

這塊內容很少,可是面試的時候可能會被提一句:**你有沒有寫過自定義指令?**因此我建議你們直接看官網:自定義指令,官網上介紹的很詳細,關於自定義指令內容大部分是使用方式、傳參細節等,沒有太多的坑在裏面。

這裏提一下我認爲有可能被深刻問到的問題:

你以爲指令和組件有什麼區別?它們各自的應用場景是什麼?

指令和組件主要的區別大概有兩點:

  1. 業務的複雜程度:

    複雜的業務不用說,確定無法單純用指令就能擺平,讓開發者只用指令寫一個彈出框出來,確定也不現實。因此複雜的業務必須組件,能夠用自定義指令來進行輔助。而且,從配置項上也能看出來,指令是包含在組件裏面的。

  2. DOM 操做的頻繁程度:

    Vue 是數據驅動視圖,自己是不提倡大量操做 DOM 的。然而實際開發場景中,咱們有不少狀況須要必須操做 DOM 。這個時候,就可使用自定義指令來進行 DOM 操做,看上去也比在業務中直接用 jquery 獲取來的優雅和語義化。常見的場景之一就是 affix 圖釘——當用戶界面滾動下來的時候,重要信息能有個吸頂的功能。

渲染函數和 JSX

從這塊內容開始,說實話,Vue 的畫風逐漸變態,已經開始在我這種白嫖黨最不喜歡的 「底層」 、「原理」 這種關鍵詞之間瘋狂試談。

裏面的內容比較多,這裏就不寫了,我自覺不可能比官網寫的好,你們仍是把這塊的官網內容看一下:渲染函數 & JSX。若是不太願意看的朋友,就看我下面的總結大概:

  1. 首先呢,咱們如今開發 vue 的基本操做就是 .vue 文件了,咱們在裏面寫的很爽,可是在最終 Vue 的 compile 編譯階段,會將咱們的組件編譯成以下的渲染函數的形式:

    <div>
        先寫一些文字
        <h1>一則頭條</h1>
        <my-component v-bind:someProp="foobar" />
    </div>
    複製代碼

    轉換爲:

    new Vue({
        render: function (createElement, context) {
            return createElement(
            'div', 
            // 一個與模板中 attribute 對應的數據對象。可選。
            {
             
            },
            // 子元素
            [
                '先寫一些文字',
                createElement('h1', '一則頭條'),
                createElement(MyComponent, {
                  props: {
                    someProp: 'foobar'
                  }
                })
            ]);
        }
    });
    複製代碼

    是否是看不懂?是否是看不懂?看不懂就對了,由於這個原本就不是給人看的,是編譯成這種格式給機器運行的。

  2. 拿到了編譯完成的渲染函數而且執行後,vue 就能獲得一個叫作 VNode虛擬 DOM,這個虛擬 DOM 其實就是 用普通 js 對象的格式,對於即將渲染成的 DOM 文檔格式的一種抽象描述。相似於下面這樣:

    {
      'class': {
        foo: true,
        bar: false
      },
      style: {
        color: 'red',
        fontSize: '14px'
      },
      // 普通的 HTML attribute
      attrs: {
        id: 'foo'
      },
      // 組件 prop
      props: {
        myProp: 'bar'
      },
      // DOM property
      domProps: {
        innerHTML: 'baz'
      },
      on: {
        click: this.clickHandler
      },
      nativeOn: {
        click: this.nativeClickHandler
      },
      children: [...]
    }
    複製代碼
  3. 有了這個 VNode Tree 的虛擬文檔描述,Vue 就能夠 將虛擬 DOM 渲染成真實的 DOM 節點,而後一次插入。

至於爲何會有這個東西呢?

  1. 首先,咱們寫的很爽的 template 這種模板語言格式,瀏覽器是不認識的,更不會執行,這個只是一種對於開發者更友好的語法糖,將真正的底層所有封裝,簡化最大程度簡化寫法,讓程序員聚焦業務。而 render 函數本質上是瀏覽器可以識別並執行的 javascript ,瀏覽器拿到就很開心。
  2. 寫過 React 的朋友就知道,Vue 和 React 的差異之一,就是在於: Vue 裏面沒辦法大量地編寫 javascript ,而 React 卻能夠。Vue 的人性化語法極大地迎合了開發者,可是這也形成了自由度沒有 React 高,畢竟 React 所有 js,就連 jsx 其實也是普通的 string 。那這種狀況下,在某些 SB需求 更爲複雜、須要高度自由的場景下,語法糖就不適用了,好比說 table 組件。那怎麼兼得魚和熊掌?vue 的 render 函數就提供了一個解決方案,可能看上去沒有那麼好看,也很差理解,可是它確實能解決你的需求。

至於最後的 JSX

這也不是 Vue 官方推薦的東西,只不過考慮到這樣的語法寫起來確實難受,官方提供了一個可供選擇的方案而已。

插件

內容很少,推薦看下官網:插件

  1. Vue.use(plugin) 註冊插件

  2. 插件須要暴露一個 install 方法,接收 Vue 構造器和 options 選項做爲參數:

    Plugin.install = function (Vue, options) {}
    複製代碼
  3. 插件裏面能夠擴展 Vue 原型、擴展全局指令、擴展全局 mixin 等。

過濾器

內容很少,推薦看下官網:過濾器

  1. 過濾器的掛載:

    new Vue({
        filters: {
            filter1 (value) {
                return value + '';
            }
        }
    });
    複製代碼
  2. 過濾器的使用:

    <div>{{ message | filter1 }}</div>
    <!--或者能夠寫成函數調用的形式-->
    <div>{{ message | filter1(arg1, arg2) }}</div>
    複製代碼
    new Vue({
        filters: {
            filter1 (value, arg1, arg2) {
                return value;
            }
        }
    });
    複製代碼
  3. 過濾器的鏈式使用:

    <!--filter1 return 去的內容,做爲參數繼續傳入 filter2-->
    <div>{{ message | filter1 | filter2 }}</div>
    複製代碼

參考文檔

相關文章
相關標籤/搜索