Vue.js 2.0 Official Guide Note

這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容。關(guān)于 Vue 1.0 ,可以參考我的另一篇文章 Vue.js 1.0 Official Guide Notes。

Vue 實(shí)例

屬性與方法

  • 不要在實(shí)例屬性或者回調(diào)函數(shù)中(如 vm.$watch('a', newVal => this.myMethod()) )使用箭頭函數(shù)。因?yàn)?strong>箭頭函數(shù)綁定父上下文,所以 this 不會(huì)像預(yù)想的一樣是 Vue 實(shí)例,而是 this.myMethod 未被定義

實(shí)例生命周期

  • 生命周期鉤子

    var vm = new Vue({
      data: {
        a: 1
      },
      created: function () {
        // `this` 指向 vm 實(shí)例
        console.log('a is: ' + this.a)
      }
    })
    // -> "a is: 1"
    
  • 也有一些其它的鉤子,在實(shí)例生命周期的不同階段調(diào)用,如 mountedupdated 、destroyed 。鉤子的 this 指向調(diào)用它的 Vue 實(shí)例。

  • Vue 沒有 Controller ,組件的自定義邏輯可以分布在這些鉤子中。

生命周期圖示

Vue Lifecycle

模板語法

插值

文本

  • 使用 v-once 指令執(zhí)行一次性插值

    <span v-once>This will never change: {{ msg }}</span>
    

純 HTML

  • 使用 v-html 指令輸出純 HTML

    <div v-html="rawHtml"></div>
    
    • 被插入的內(nèi)容都會(huì)被當(dāng)做 HTML ,數(shù)據(jù)綁定會(huì)被忽略
    • 不能使用 v-html 來復(fù)合局部模板。
    • 只對(duì)可信內(nèi)容使用 HTML 插值,絕不要對(duì)用戶提供的內(nèi)容插值。(避免 XSS )

屬性

  • Mustache 不能在 HTML 屬性中使用,應(yīng)使用 v-bind 指令(包括布爾值)。

    <div v-bind:id="dynamicId"></div>
    

使用 JavaScript 表達(dá)式

  • Vue.js 對(duì)于所有的數(shù)據(jù)綁定都提供了完全的 JavaScript 表達(dá)式支持。
  • 每個(gè)綁定只能包含單個(gè)表達(dá)式,不能是語句(比如:賦值語句)也不能是流程控制(使用三元表達(dá)式替代)。
  • 模板表達(dá)式被放在沙盒中,只能訪問全局變量的一個(gè)白名單,如 MathDate 。不能模板表達(dá)式中訪問用戶定義的全局變量。

指令

  • 指令(Directives)是帶有 v- 前綴的特殊屬性。其屬性的值預(yù)期是單一 JavaScript 表達(dá)式(除了 v-for )。
  • 指令的職責(zé)就是當(dāng)其表達(dá)式的值改變時(shí)相應(yīng)地將某些行為應(yīng)用到 DOM 上。

參數(shù)

  • 一些指令能接受一個(gè)“參數(shù)”,在指令后以冒號(hào)指明。
    • v-bind 指令被用來響應(yīng)地更新 HTML 屬性:

      <a v-bind:href="url"></a>
      
    • v-on 指令,它用于監(jiān)聽 DOM 事件:

      <a v-on:click="doSomething">
      

修飾符

  • 修飾符(Modifiers)是以半角句號(hào) . 指明的特殊后綴,用于指出一個(gè)指令應(yīng)該以特殊方式綁定。
    • .prevent 修飾符告訴 v-on 指令對(duì)于觸發(fā)的事件調(diào)用 event.preventDefault()

      <form v-on:submit.prevent="onSubmit"></form>
      

過濾器

  • 過濾器被用作一些常見的文本格式化。

  • 過濾器可以用在兩個(gè)地方:mustache 插值和 v-bind 表達(dá)式。

  • 過濾器應(yīng)該被添加在 JavaScript 表達(dá)式的尾部,由“管道”符指示。

    <!-- in mustaches -->
    {{ message | capitalize }}
    <!-- in v-bind -->
    <div v-bind:id="rawId | formatId"></div>
    
  • 在 Vue 2 中,過濾器只能在 mustache 綁定和 v-bind 表達(dá)式中使用,對(duì)于更復(fù)雜的數(shù)據(jù)變換應(yīng)當(dāng)使用計(jì)算屬性

  • 過濾器函數(shù)總接受表達(dá)式的值作為第一個(gè)參數(shù)。

    new Vue({
      // ...
      filters: {
        capitalize: function (value) {
          if (!value) return ''
          value = value.toString()
          return value.charAt(0).toUpperCase() + value.slice(1)
        }
      }
    })
    
  • 過濾器可以串聯(lián)。

    {{ message | filterA | filterB }}
    
  • 過濾器是 JavaScript 函數(shù),因此可以接受參數(shù)。

    {{ message | filterA('arg1', arg2) }}
    // 字符串 'arg1' 將傳給過濾器作為第二個(gè)參數(shù), arg2 表達(dá)式的值將被求值然后傳給過濾器作為第三個(gè)參數(shù)
    

計(jì)算屬性

計(jì)算屬性

  • 在模板中放入太多的邏輯會(huì)讓模板過重且難以維護(hù)。

計(jì)算屬性 vs Methods

  • 對(duì)于最終結(jié)果,計(jì)算屬性與 Method 是相同的。

  • 計(jì)算屬性是基于它們的依賴進(jìn)行緩存的,其只有在它的相關(guān)依賴發(fā)生改變時(shí)才會(huì)重新求值。

    • 當(dāng)然這也意味著有些計(jì)算屬性求值后將不會(huì)更新,比如 Date.now() ,因?yàn)樗皇琼憫?yīng)式依賴。

      computed: {
        now: function () {
          return Date.now()
        }
      }
      
  • 相比而言,只要發(fā)生重新渲染,method 調(diào)用總會(huì)執(zhí)行該函數(shù)。

Computed 屬性 vs Watched 屬性

  • Computed 屬性能更好的處理多個(gè)數(shù)據(jù)的變動(dòng),而不需要為每一個(gè)數(shù)據(jù)單獨(dú)書寫 watch 函數(shù)。

計(jì)算 setter

  • 計(jì)算屬性默認(rèn)只有 getter ,但允許提供 setter 。

    // ...
    computed: {
      fullName: {
        // getter
        get: function () {
          return this.firstName + ' ' + this.lastName
        },
        // setter
        set: function (newValue) {
          var names = newValue.split(' ')
          this.firstName = names[0]
          this.lastName = names[names.length - 1]
        }
      }
    }
    // ...
    
  • 現(xiàn)在在運(yùn)行 vm.fullName = 'John Doe' 時(shí), setter 會(huì)被調(diào)用, vm.firstNamevm.lastName 也相應(yīng)地會(huì)被更新。

觀察 Watchers

  • 當(dāng)想要在數(shù)據(jù)變化響應(yīng)時(shí),執(zhí)行異步操作或開銷較大的操作,watch 會(huì)很實(shí)用。

  • 舉個(gè)栗子:

    <div id="watch-example">
      <p>
        Ask a yes/no question:
        <input v-model="question">
      </p>
      <p>{{ answer }}</p>
    </div>
    
    <!-- Since there is already a rich ecosystem of ajax libraries    -->
    <!-- and collections of general-purpose utility methods, Vue core -->
    <!-- is able to remain small by not reinventing them. This also   -->
    <!-- gives you the freedom to just use what you're familiar with. -->
    <script src="https://unpkg.com/axios@0.12.0/dist/axios.min.js"></script>
    <script src="https://unpkg.com/lodash@4.13.1/lodash.min.js"></script>
    <script>
    var watchExampleVM = new Vue({
      el: '#watch-example',
      data: {
        question: '',
        answer: 'I cannot give you an answer until you ask a question!'
      },
      watch: {
        // 如果 question 發(fā)生改變,這個(gè)函數(shù)就會(huì)運(yùn)行
        question: function (newQuestion) {
          this.answer = 'Waiting for you to stop typing...'
          this.getAnswer()
        }
      },
      methods: {
        // _.debounce 是一個(gè)通過 lodash 限制操作頻率的函數(shù)。
        // 在這個(gè)例子中,我們希望限制訪問yesno.wtf/api的頻率
        // ajax請(qǐng)求直到用戶輸入完畢才會(huì)發(fā)出
        // 學(xué)習(xí)更多關(guān)于 _.debounce function (and its cousin
        // _.throttle), 參考: https://lodash.com/docs#debounce
        getAnswer: _.debounce(
          function () {
            var vm = this
            if (this.question.indexOf('?') === -1) {
              vm.answer = 'Questions usually contain a question mark. ;-)'
              return
            }
            vm.answer = 'Thinking...'
            axios.get('https://yesno.wtf/api')
              .then(function (response) {
                vm.answer = _.capitalize(response.data.answer)
              })
              .catch(function (error) {
                vm.answer = 'Error! Could not reach the API. ' + error
              })
          },
          // 這是我們?yōu)橛脩敉V馆斎氲却暮撩霐?shù)
          500
        )
      }
    })
    </script>
    
  • 在這個(gè)示例中,使用 watch 選項(xiàng)允許我們執(zhí)行異步操作(訪問一個(gè) API),限制我們執(zhí)行該操作的頻率,并在我們得到最終結(jié)果前,設(shè)置中間狀態(tài)。這是計(jì)算屬性無法做到的。

  • 除了 watch 選項(xiàng)之外,還有 vm.$watch 命令。

Class 與 Style 綁定

綁定 HTML Class

對(duì)象語法

  • v-bind:class 指令可以與普通的 class 屬性共存。

  • 可以直接綁定數(shù)據(jù)里的一個(gè)對(duì)象。

    <div v-bind:class="classObject"></div>
    
    data: {
      classObject: {
        active: true,
        'text-danger': false
      }
    }
    
  • 也可以在這里綁定返回對(duì)象的計(jì)算屬性。

    <div v-bind:class="classObject"></div>
    
    data: {
      isActive: true,
      error: null
    },
    computed: {
      classObject: function () {
        return {
          active: this.isActive && !this.error,
          'text-danger': this.error && this.error.type === 'fatal',
        }
      }
    }
    

數(shù)組語法

  • 可以把一個(gè)數(shù)組傳給 v-bind:class ,以應(yīng)用一個(gè) class 列表。

  • 如果需要根據(jù)條件切換列表中的 class ,可以用三元表達(dá)式。

    <div v-bind:class="[isActive ? activeClass : '', errorClass]">
    // 始終添加 errorClass ,但只有在 isActive 是 true 時(shí)添加 activeClass
    
  • 可以在數(shù)組語法中使用對(duì)象語法。

    <div v-bind:class="[{ active: isActive }, errorClass]">
    

用在組件上

  • 在一個(gè)定制的組件上用到 class 屬性的時(shí)候,這些類將被添加到根元素上面,這個(gè)元素上已經(jīng)存在的類不會(huì)被覆蓋。

綁定內(nèi)聯(lián)樣式

  • 和 Class 類似。

條件渲染

v-if

  • 使用 v-if 選擇性的渲染(不是顯示)元素。

v-else-if

<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-else ,v-else-if 必須緊跟在 v-if 或者 v-else-if 元素之后。

key 管理可復(fù)用的元素

  • Vue 通常會(huì)復(fù)用已有元素而不是從頭開始渲染,以提升運(yùn)行效率。

  • 例如:如果允許用戶在不同的登錄方式之間切換

    <template v-if="loginType === 'username'">
      <label>Username</label>
      <input placeholder="Enter your username">
    </template>
    <template v-else>
      <label>Email</label>
      <input placeholder="Enter your email address">
    </template>
    
    • 上面的代碼中切換 loginType 將不會(huì)清除用戶已經(jīng)輸入的內(nèi)容。因?yàn)閮蓚€(gè)模版使用了相同的元素,<input> 不會(huì)被替換掉——僅僅是替換了它的的 placeholder 。
  • 有些情況下這樣做不符合需求。Vue 提供了一種方式來聲明兩個(gè)元素的獨(dú)立性:添加一個(gè)具有唯一值的 key 屬性。

    <template v-if="loginType === 'username'">
      <label>Username</label>
      <input placeholder="Enter your username" key="username-input">
    </template>
    <template v-else>
      <label>Email</label>
      <input placeholder="Enter your email address" key="email-input">
    </template>
    
    • 注意,<label> 元素仍然會(huì)被高效地復(fù)用,因?yàn)樗鼈儧]有添加 key 屬性。

v-show

  • 使用 v-show 根據(jù)條件展示元素。帶有 v-show 的元素始終會(huì)被渲染并保留在 DOM 中。v-show 是簡(jiǎn)單地切換元素的 CSS 屬性 display 。
  • v-show 不支持 <template> 語法,也不支持 v-else

v-if vs v-show

  • 一般來說, v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運(yùn)行時(shí)條件不太可能改變,則使用 v-if 較好。

v-ifv-for 一起使用

  • 當(dāng) v-ifv-for 一起使用時(shí),v-for 具有比 v-if 更高的優(yōu)先級(jí)。

列表渲染

v-for

基本用法

  • v-for 塊中,我們擁有對(duì)父作用域?qū)傩缘?strong>完全訪問權(quán)限。 v-for 還支持一個(gè)可選的第二個(gè)參數(shù)為當(dāng)前項(xiàng)的索引。

    <ul id="example-2">
      <li v-for="(item, index) in items">
        {{ parentMessage }} - {{ index }} - {{ item.message }}
      </li>
    </ul>
    
    var example2 = new Vue({
      el: '#example-2',
      data: {
        parentMessage: 'Parent',
        items: [
          { message: 'Foo' },
          { message: 'Bar' }
        ]
      }
    })
    
  • 可以用 of 替代 in 作為分隔符。

Template v-for

  • 可以用帶有 v-for<template> 標(biāo)簽來渲染多個(gè)元素塊。

    <ul>
      <template v-for="item in items">
        <li>{{ item.msg }}</li>
        <li class="divider"></li>
      </template>
    </ul>
    

對(duì)象迭代 v-for

  • 可以用 v-for 通過一個(gè)對(duì)象的屬性來迭代。

    <ul id="repeat-object" class="demo">
      <li v-for="value in object">
        {{ value }}
      </li>
    </ul>
    
    new Vue({
      el: '#repeat-object',
      data: {
        object: {
          FirstName: 'John',
          LastName: 'Doe',
          Age: 30
        }
      }
    })
    
  • 也可以提供第二個(gè)的參數(shù)為鍵名,第三個(gè)參數(shù)作為索引。

    <div v-for="(value, key) in object">
      {{ key }} : {{ value }}
    </div>
    
    <div v-for="(value, key, index) in object">
      {{ index }}. {{ key }} : {{ value }}
    </div>
    
  • 在遍歷對(duì)象時(shí),是按 Object.keys() 的結(jié)果遍歷,但是不能保證它的結(jié)果在不同的 JavaScript 引擎下是一致的。

整數(shù)迭代 v-for

<div>
  <span v-for="n in 10">{{ n }}</span>
</div>

組件 和 v-for

  • 在自定義組件中,可以像普通元素一樣用 v-for 。然而他不能自動(dòng)傳遞數(shù)據(jù)到組件里,因?yàn)榻M件有自己獨(dú)立的作用域。

  • 使用 props 傳遞迭代數(shù)據(jù)到組件里。

    <my-component
      v-for="(item, index) in items"
      v-bind:item="item"
      v-bind:index="index">
    </my-component>
    
  • 不自動(dòng)注入 item 到組件里的原因是,因?yàn)檫@使得組件會(huì)緊密耦合到 v-for 如何運(yùn)作。在一些情況下,明確數(shù)據(jù)的來源可以使組件可重用。

key

  • 當(dāng) Vue.js 用 v-for 正在更新已渲染過的元素列表時(shí),它默認(rèn)用 “就地復(fù)用“ 策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不是移動(dòng) DOM 元素來匹配數(shù)據(jù)項(xiàng)的順序, 而是簡(jiǎn)單復(fù)用此處每個(gè)元素,并且確保它在特定索引下顯示已被渲染過的每個(gè)元素。這個(gè)類似 Vue 1.x 的 track-by="$index" 。

  • 這個(gè)默認(rèn)的模式是有效的,但是只適用于不依賴子組件狀態(tài)或臨時(shí) DOM 狀態(tài)(例如:表單輸入值)的列表渲染輸出。

  • 為了給 Vue 一個(gè)提示,以便它能跟蹤每個(gè)節(jié)點(diǎn)的身份,從而重用和重新排序現(xiàn)有元素,要為每項(xiàng)提供一個(gè)唯一 key 屬性。理想的 key 值是每項(xiàng)都有唯一 id。這個(gè)特殊的屬性相當(dāng)于 Vue 1.x 的 track-by ,但它的工作方式類似于一個(gè)屬性,所以要用 v-bind 來綁定動(dòng)態(tài)值。

    <div v-for="item in items" :key="item.id">
      <!-- 內(nèi)容 -->
    </div>
    
  • 建議盡可能使用 v-for 來提供 key ,除非迭代 DOM 內(nèi)容足夠簡(jiǎn)單,或者你是故意要依賴于默認(rèn)行為來獲得性能提升。

  • key 并不特別與 v-for 關(guān)聯(lián),key 還具有其他用途。

數(shù)組更新檢測(cè)

變異方法

  • 修改當(dāng)前數(shù)組的方法,查閱 API。

重塑數(shù)組

  • 使用一個(gè)新的數(shù)組代替原始數(shù)組。
    • Vue 實(shí)現(xiàn)了一些智能啟發(fā)式方法來最大化 DOM 元素重用,所以用一個(gè)含有相同元素的數(shù)組去替換原來的數(shù)組是非常高效的操作。

注意事項(xiàng)

  • 由于 JavaScript 的限制, Vue 不能檢測(cè)以下變動(dòng)的數(shù)組:
    • 當(dāng)你利用索引直接設(shè)置一個(gè)項(xiàng)時(shí),例如:vm.items[indexOfItem] = newValue
    • 當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:vm.items.length = newLength

顯示過濾 / 排序結(jié)果

  • 通過創(chuàng)建返回過濾或排序數(shù)組的計(jì)算屬性來顯示一個(gè)數(shù)組的過濾或排序副本,而不實(shí)際改變或重置原始數(shù)據(jù)。

    <li v-for="n in evenNumbers">{{ n }}</li>
    
    data: {
      numbers: [ 1, 2, 3, 4, 5 ]
    },
    computed: {
      evenNumbers: function () {
        return this.numbers.filter(function (number) {
          return number % 2 === 0
        })
      }
    }
    
  • 在計(jì)算屬性不適用的情況下 (例如,在嵌套 v-for 循環(huán)中) 使用 method 方法實(shí)現(xiàn)。

    <li v-for="n in even(numbers)">{{ n }}</li>
    
    data: {
      numbers: [ 1, 2, 3, 4, 5 ]
    },
    methods: {
      even: function (numbers) {
        return numbers.filter(function (number) {
          return number % 2 === 0
        })
      }
    }
    

事件處理器

內(nèi)聯(lián)處理器方法

  • 有時(shí)也需要在內(nèi)聯(lián)語句處理器中訪問原生 DOM 事件。可以用特殊變量 $event 把它傳入方法:(使用內(nèi)聯(lián)語句的時(shí)候因?yàn)榘远▍?shù),默認(rèn)不傳遞 event

    <button v-on:click="warn('Form cannot be submitted yet.', $event)">Submit</button>
    
    // ...
    methods: {
      warn: function (message, event) {
        // 現(xiàn)在我們可以訪問原生事件對(duì)象
        if (event) event.preventDefault()
        alert(message)
      }
    }
    

事件修飾符

  • Vue.js 為 v-on 提供了 事件修飾符。通過由點(diǎn)( . )表示的指令后綴來調(diào)用修飾符。

    <!-- 阻止單擊事件冒泡 -->
    <a v-on:click.stop="doThis"></a>
    <!-- 提交事件不再重載頁面 -->
    <form v-on:submit.prevent="onSubmit"></form>
    <!-- 修飾符可以串聯(lián)  -->
    <a v-on:click.stop.prevent="doThat"></a>
    <!-- 只有修飾符 -->
    <form v-on:submit.prevent></form>
    <!-- 添加事件偵聽器時(shí)使用事件捕獲模式 -->
    <div v-on:click.capture="doThis">...</div>
    <!-- 只當(dāng)事件在該元素本身(而不是子元素)觸發(fā)時(shí)觸發(fā)回調(diào) -->
    <div v-on:click.self="doThat">...</div>
    <!-- 2.14 新增:點(diǎn)擊事件將只會(huì)觸發(fā)一次 -->
    <a v-on:click.once="doThis"></a>
    
  • .once 修飾符還能被用到自定義的組件事件上。如果你還沒有閱讀關(guān)于組件的文檔,現(xiàn)在大可不必?fù)?dān)心。

按鍵修飾符

  • Vue 允許為 v-on 在監(jiān)聽鍵盤事件時(shí)添加按鍵修飾符。

    <!-- 只有在 keyCode 是 13 時(shí)調(diào)用 vm.submit() -->
    <input v-on:keyup.13="submit">
    
  • 記住所有的 keyCode 比較困難,所以 Vue 為最常用的按鍵提供了別名。

    <!-- 同上 -->
    <input v-on:keyup.enter="submit">
    <!-- 縮寫語法 -->
    <input @keyup.enter="submit">
    
  • 全部按鍵別名

  • 可以通過全局 config.keyCodes 對(duì)象自定義按鍵修飾符別名。

    // 可以使用 v-on:keyup.f1
    Vue.config.keyCodes.f1 = 112
    
  • 注意:在Mac系統(tǒng)鍵盤上,meta對(duì)應(yīng)命令鍵 (?)。在Windows系統(tǒng)鍵盤meta對(duì)應(yīng)windows徽標(biāo)鍵(?)。在Sun操作系統(tǒng)鍵盤上,meta對(duì)應(yīng)實(shí)心寶石鍵 (◆)。在其他特定鍵盤上,尤其在MIT和Lisp鍵盤及其后續(xù),比如Knight鍵盤,space-cadet鍵盤,meta被標(biāo)記為“META”。在Symbolics鍵盤上,meta被標(biāo)記為“META” 或者 “Meta”。

為什么在 HTML 中監(jiān)聽事件?

  • 當(dāng)一個(gè) ViewModel 被銷毀時(shí),所有的事件處理器都會(huì)自動(dòng)被刪除。你無須擔(dān)心如何自己清理它們。

表單控件綁定

基礎(chǔ)用法

  • 對(duì)于要求 IME (如中文、 日語、 韓語等) 的語言,你會(huì)發(fā)現(xiàn) v-model 不會(huì)在 ime 構(gòu)成中得到更新。如果你也想實(shí)現(xiàn)更新,請(qǐng)使用 input 事件。

綁定 value

  • 對(duì)于單選按鈕,勾選框及選擇列表選項(xiàng), v-model 綁定的 value 通常是靜態(tài)字符串(對(duì)于勾選框是邏輯值)。
  • 可以用 v-bind 綁定 value 到 Vue 實(shí)例的一個(gè)動(dòng)態(tài)屬性上,這個(gè)屬性的值可以不是字符串。

復(fù)選框

<input
  type="checkbox"
  v-model="toggle"
  v-bind:true-value="a"
  v-bind:false-value="b"
>
// 當(dāng)選中時(shí)
vm.toggle === vm.a
// 當(dāng)沒有選中時(shí)
vm.toggle === vm.b

單選按鈕

<input type="radio" v-model="pick" v-bind:value="a">
// 當(dāng)選中時(shí)
vm.pick === vm.a

選擇列表設(shè)置

<select v-model="selected">
    <!-- 內(nèi)聯(lián)對(duì)象字面量 -->
  <option v-bind:value="{ number: 123 }">123</option>
</select>
// 當(dāng)選中時(shí)
typeof vm.selected // -> 'object'
vm.selected.number // -> 123

修飾符

.lazy

  • 在默認(rèn)情況下,v-modelinput 事件中同步輸入框的值與數(shù)據(jù) (除了 上述 IME 部分),但你可以添加一個(gè)修飾符 lazy ,從而轉(zhuǎn)變?yōu)樵?change 事件中同步。

    <!-- 在 "change" 而不是 "input" 事件中更新 -->
    <input v-model.lazy="msg" >
    

.number

  • 如果想自動(dòng)將用戶的輸入值轉(zhuǎn)為 Number 類型(如果原值的轉(zhuǎn)換結(jié)果為 NaN 則返回原值),可以添加一個(gè)修飾符 numberv-model 來處理輸入值。
    • 這通常很有用,因?yàn)樵?type="number" 時(shí) HTML 中輸入的值也總是會(huì)返回字符串類型。

.trim

  • 如果要自動(dòng)過濾用戶輸入的首尾空格,可以添加 trim 修飾符到 v-model 上過濾輸入。

    <input v-model.trim="msg">
    

v-model 與組件

  • Vue 的組件系統(tǒng)允許你創(chuàng)建一個(gè)具有自定義行為可復(fù)用的 input 類型,這些 input 類型甚至可以和 v-model 一起使用!要了解更多,請(qǐng)參閱自定義 input 類型

組件

使用組件

DOM 模板解析說明

  • 當(dāng)使用 DOM 作為模版時(shí)(例如,將 el 選項(xiàng)掛載到一個(gè)已存在的元素上), 你會(huì)受到 HTML 的一些限制,因?yàn)?Vue 只有在瀏覽器解析和標(biāo)準(zhǔn)化 HTML 后才能獲取模版內(nèi)容。尤其像這些元素 <ul> , <ol><table> , <select> 限制了能被它包裹的元素, <option> 只能出現(xiàn)在其它元素內(nèi)部。

  • 在自定義組件中使用這些受限制的元素時(shí)會(huì)導(dǎo)致一些問題,例如:

    <table>
      <my-row>...</my-row>
    </table>
    

    自定義組件 <my-row> 被認(rèn)為是無效的內(nèi)容,因此在渲染的時(shí)候會(huì)導(dǎo)致錯(cuò)誤。變通的方案是使用特殊的 is 屬性:

    <table>
      <tr is="my-row"></tr>
    </table>
    
  • 應(yīng)當(dāng)注意,如果您使用來自以下來源之一的字符串模板,這些限制將不適用:

    • <script type="text/x-template">
    • JavaScript內(nèi)聯(lián)模版字符串
    • .vue 組件

data 必須是函數(shù)

  • 通過 Vue 構(gòu)造器傳入的各種選項(xiàng)大多數(shù)都可以在組件里用。data 是一個(gè)例外,它必須是函數(shù)。

構(gòu)成組件

  • 在 Vue.js 中,父子組件的關(guān)系可以總結(jié)為 props down, events up 。父組件通過 props 向下傳遞數(shù)據(jù)給子組件,子組件通過 events 給父組件發(fā)送消息。
    父子組件通信流程

Prop

camelCase vs kebab-case

  • 使用字符串模板時(shí),不需要將 camelCase 轉(zhuǎn)換成 kebab-case 。

字面量語法 vs 動(dòng)態(tài)語法

  • 使用字面量語法傳遞數(shù)值會(huì)得到字符串。

    <!-- 傳遞了一個(gè)字符串"1" -->
    <comp some-prop="1"></comp>
    
  • 如果想傳遞一個(gè)實(shí)際的 number ,需要使用 v-bind ,從而讓它的值被當(dāng)作 JavaScript 表達(dá)式計(jì)算。

    <!-- 傳遞實(shí)際的mumber -->
    <comp v-bind:some-prop="1"></comp>
    

單向數(shù)據(jù)流

  • 不應(yīng)該在子組件內(nèi)部改變 prop ,Vue 會(huì)在控制臺(tái)給出警告。

  • prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來用:定義一個(gè)局部變量,并用 prop 的值初始化它。

    props: ['initialCounter'],
    data: function () {
      return { counter: this.initialCounter }
    }
    
  • prop 作為初始值傳入,由子組件處理成其它數(shù)據(jù)輸出:定義一個(gè)計(jì)算屬性,處理 prop 的值并返回。

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    
  • 在 JavaScript 中對(duì)象和數(shù)組是引用類型,指向同一個(gè)內(nèi)存空間,如果 prop 是一個(gè)對(duì)象或數(shù)組,在子組件內(nèi)部改變它會(huì)影響父組件的狀態(tài)。

Prop 驗(yàn)證

  • 我們可以為組件的 props 指定驗(yàn)證規(guī)格。如果傳入的數(shù)據(jù)不符合規(guī)格,Vue 會(huì)發(fā)出警告。要指定驗(yàn)證規(guī)格,需要用對(duì)象的形式,而不能用字符串?dāng)?shù)組。

    Vue.component('example', {
      props: {
        // 基礎(chǔ)類型檢測(cè) (`null` 意思是任何類型都可以)
        propA: Number,
        // 多種類型
        propB: [String, Number],
        // 必傳且是字符串
        propC: {
          type: String,
          required: true
        },
        // 數(shù)字,有默認(rèn)值
        propD: {
          type: Number,
          default: 100
        },
        // 數(shù)組/對(duì)象的默認(rèn)值應(yīng)當(dāng)由一個(gè)工廠函數(shù)返回
        propE: {
          type: Object,
          default: function () {
            return { message: 'hello' }
          }
        },
        // 自定義驗(yàn)證函數(shù)
        propF: {
          validator: function (value) {
            return value > 10
          }
        }
      }
    })
    
  • type 可以是 String Number Boolean Function Object Array ,type 也可以是一個(gè)自定義構(gòu)造器函數(shù),使用 instanceof 檢測(cè)。

  • 當(dāng) prop 驗(yàn)證失敗,Vue會(huì)在拋出警告 (如果使用的是開發(fā)版本)。

自定義事件

  • 子組件通過自定義事件向父組件回傳數(shù)據(jù)。

  • 每個(gè) Vue 實(shí)例都實(shí)現(xiàn)了事件接口:

    • 使用 $on(eventName) 監(jiān)聽事件。
    • 使用 $emit(eventName) 觸發(fā)事件。
  • Vue 的事件系統(tǒng)分離自瀏覽器的 EventTarget API 。盡管它們的運(yùn)行類似,但是$on$emit 不是 addEventListenerdispatchEvent 的別名。

  • 父組件可以在使用子組件的地方直接用 v-on 來監(jiān)聽子組件觸發(fā)的事件。

    • 不能$on 偵聽子組件拋出的事件,而必須在模板里直接用 v-on 綁定。
    <div id="counter-event-example">
      <p>{{ total }}</p>
      <button-counter v-on:increment="incrementTotal"></button-counter>
      <button-counter v-on:increment="incrementTotal"></button-counter>
    </div>
    
    Vue.component('button-counter', {
      template: '<button v-on:click="increment">{{ counter }}</button>',
      data: function () {
        return {
          counter: 0
        }
      },
      methods: {
        increment: function () {
          this.counter += 1
          this.$emit('increment')
        }
      },
    })
    new Vue({
      el: '#counter-event-example',
      data: {
        total: 0
      },
      methods: {
        incrementTotal: function () {
          this.total += 1
        }
      }
    })
    

    子組件已經(jīng)和它外部完全解耦了。它所做的只是報(bào)告自己的內(nèi)部事件,至于父組件是否關(guān)心則與它無關(guān)。留意到這一點(diǎn)很重要。

  • 在某個(gè)組件的根元素上監(jiān)聽一個(gè)原生事件??梢允褂?.native 修飾 v-on 。

    <my-component v-on:click.native="doTheThing"></my-component>
    

使用自定義事件處理表單輸入組件

  • 自定義事件可以用來創(chuàng)建自定義的表單輸入組件,使用 v-model 來進(jìn)行數(shù)據(jù)雙向綁定。

    <input v-model="something">
    

    等價(jià)于:

    <input v-bind:value="something" v-on:input="something = $event.target.value">
    

    在組件中使用時(shí),它相當(dāng)于下面的簡(jiǎn)寫:

    <custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>
    
  • 要讓組件的 v-model 生效,它必須:

    • 接受一個(gè) value 屬性。
    • 在有新的 value 時(shí)觸發(fā) input 事件。

非父子組件通信

  • 在簡(jiǎn)單的場(chǎng)景下,可以使用一個(gè)空的 Vue 實(shí)例作為中央事件總線。

    var bus = new Vue()
    s = new Vue()
    
    // 觸發(fā)組件 A 中的事件
    bus.$emit('id-selected', 1)
    
    // 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
    bus.$on('id-selected', function (id) {
      // ...
    })
    
  • 在復(fù)雜的情況下,考慮使用專門的狀態(tài)管理模式

使用 slot 分發(fā)內(nèi)容

作用域插槽

  • 作用域插槽是一種特殊類型的插槽,用作使用一個(gè)(能夠傳遞數(shù)據(jù)到)可重用模板替換已渲染元素。

  • 在子組件中,只需將數(shù)據(jù)傳遞到插槽,就像你將 prop 傳遞給組件一樣:

    <div class="child">
      <slot text="hello from child"></slot>
    </div>
    
  • 在父級(jí)中,具有特殊屬性 scope<template> 元素,表示它是作用域插槽的模板。scope 的值對(duì)應(yīng)一個(gè)臨時(shí)變量名,此變量接收從子組件中傳遞的 prop 對(duì)象:

    <div class="parent">
      <child>
        <template scope="props">
          <span>hello from parent</span>
          <span>{{ props.text }}</span>
        </template>
      </child>
    </div>
    
  • 渲染結(jié)果:

    <div class="parent">
      <div class="child">
        <span>hello from parent</span>
        <span>hello from child</span>
      </div>
    </div>
    
  • 作用域插槽更具代表性的用例是列表組件,允許組件自定義應(yīng)該如何渲染列表每一項(xiàng):

    <my-awesome-list :items="items">
      <!-- 作用域插槽也可以在這里命名 -->
      <template slot="item" scope="props">
        <li class="my-fancy-item">{{ props.text }}</li>
      </template>
    </my-awesome-list>
    

    列表組件模板:

    <ul>
      <slot name="item"
        v-for="item in items"
        :text="item.text">
        <!-- fallback content here -->
      </slot>
    </ul>
    

動(dòng)態(tài)組件

  • 通過使用保留的 <component> 元素,動(dòng)態(tài)地綁定到它的 is 特性,我們讓多個(gè)組件可以使用同一個(gè)掛載點(diǎn),并動(dòng)態(tài)切換:

    var vm = new Vue({
      el: '#example',
      data: {
        currentView: 'home'
      },
      components: {
        home: { /* ... */ },
        posts: { /* ... */ },
        archive: { /* ... */ }
      }
    })
    
    <component v-bind:is="currentView">
      <!-- 組件在 vm.currentview 變化時(shí)改變! -->
    </component>
    

    也可以直接綁定到組件對(duì)象上:

    var Home = {
      template: '<p>Welcome home!</p>'
    }
    var vm = new Vue({
      el: '#example',
      data: {
        currentView: Home
      }
    })
    

keep-alive

  • 如果把切換出去的組件保留在內(nèi)存中,可以保留它的狀態(tài)或避免重新渲染。為此可以添加一個(gè) keep-alive 指令參數(shù):

    <keep-alive>
      <component :is="currentView">
        <!-- 非活動(dòng)組件將被緩存! -->
      </component>
    </keep-alive>
    

雜項(xiàng)

子組件索引

  • 可以使用 ref 為子組件指定一個(gè)索引 ID 以在 JavaScript 中直接訪問子組件。

    <div id="parent">
      <user-profile ref="profile"></user-profile>
    </div>
    
    var parent = new Vue({ el: '#parent' })
    // 訪問子組件
    var child = parent.$refs.profile
    
  • 當(dāng) ref 和 v-for 一起使用時(shí), ref 是一個(gè)數(shù)組或?qū)ο螅鄳?yīng)的子組件。

  • $refs 只在組件渲染完成后才填充,并且它是非響應(yīng)式的。它僅僅作為一個(gè)直接訪問子組件的應(yīng)急方案——應(yīng)當(dāng)避免在模版或計(jì)算屬性中使用 $refs 。

異步組件

  • Vue.js 允許將組件定義為一個(gè)工廠函數(shù),動(dòng)態(tài)地解析組件的定義。Vue.js 只在組件需要渲染時(shí)觸發(fā)工廠函數(shù),并且把結(jié)果緩存起來,用于后面的再次渲染。

  • 一個(gè)例子

    Vue.component('async-example', function (resolve, reject)   {
      setTimeout(function () {
        resolve({
          template: '<div>I am async!</div>'
        })
      }, 1000)
    })
    

    工廠函數(shù)接收一個(gè) resolve 回調(diào),在收到從服務(wù)器下載的組件定義時(shí)調(diào)用。也可以調(diào)用 reject(reason) 指示加載失敗。這里 setTimeout 只是為了演示。怎么獲取組件完全由你決定。推薦配合使用 Webpack 的代碼分割功能

    Vue.component('async-webpack-example', function (resolve)   {
      // 這個(gè)特殊的 require 語法告訴 webpack
      // 自動(dòng)將編譯后的代碼分割成不同的塊,
      // 這些塊將通過 ajax 請(qǐng)求自動(dòng)下載。
      require(['./my-async-component'], resolve)
    })
    

遞歸組件

  • name 選項(xiàng)的組件可以在模板內(nèi)部遞歸調(diào)用自己。

    var StackOverflow = Vue.extend({
      name: 'stack-overflow',
      template:
        '<div>' +
          // 遞歸地調(diào)用它自己
          '<stack-overflow></stack-overflow>' +
        '</div>'
    })
    

    上面組件會(huì)導(dǎo)致一個(gè)錯(cuò)誤 “max stack size exceeded”,所以要確保遞歸調(diào)用有終止條件

組件間的循環(huán)引用(Circular References Between Components)

內(nèi)聯(lián)模板

  • 如果子組件有 inline-template 特性,組件將把它的內(nèi)容當(dāng)做它的模板,而不是分發(fā)內(nèi)容。

    <my-component inline-template>
      <p>These are compiled as the component's own template</p>
      <p>Not parent's transclusion content.</p>
    </my-component>
    
  • inline-template 讓模板的作用域難以理解,并且不能緩存模板編譯結(jié)果。最佳實(shí)踐是使用 template 選項(xiàng)在組件內(nèi)定義模板或者在 .vue 文件中使用 template 元素。

X-Templates

  • 另一種定義模版的方式是在 JavaScript 標(biāo)簽里使用 text/x-template 類型,并且指定一個(gè)id。

    <script type="text/x-template" id="hello-world-template">
      <p>Hello hello hello</p>
    </script>
    
    Vue.component('hello-world', {
      template: '#hello-world-template'
    })
    
  • 這在有很多模版或者小的應(yīng)用中有用,否則應(yīng)該避免使用,因?yàn)樗鼘⒛0婧徒M件的其他定義隔離了。

對(duì)低開銷的靜態(tài)組件使用 v-once

  • 盡管在 Vue 中渲染 HTML 很快,不過當(dāng)組件中包含大量靜態(tài)內(nèi)容時(shí),可以考慮使用 v-once 將渲染結(jié)果緩存起來。

    Vue.component('terms-of-service', {
      template: '\
        <div v-once>\
          <h1>Terms of Service</h1>\
          ... a lot of static content ...\
        </div>\
      '
    })
    

深入響應(yīng)式原理

  • Vue 使用 Object.defineProperty 把 Vue 實(shí)例的 data 中的屬性全部轉(zhuǎn)換為 getter / setter 。
  • 在內(nèi)部,Vue 會(huì)追蹤依賴,在屬性被訪問和修改時(shí)通知變化。
  • 每個(gè)組件實(shí)例都有相應(yīng)的 watcher 實(shí)例對(duì)象,它會(huì)在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的 setter 被調(diào)用時(shí),會(huì)通知 watcher 重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。

變化檢測(cè)問題

  • 由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì)屬性執(zhí)行 getter/setter 轉(zhuǎn)化過程,所以屬性必須在 data 對(duì)象上存在才能讓 Vue 轉(zhuǎn)換它,這樣才能讓它是響應(yīng)的。

  • Vue 不允許在已經(jīng)創(chuàng)建的實(shí)例上動(dòng)態(tài)添加新的根級(jí)響應(yīng)式屬性(root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 方法將響應(yīng)屬性添加到嵌套的對(duì)象上,也可以使用 vm.$set 實(shí)例方法,這也是全局 Vue.set 方法的別名。

    Vue.set(vm.someObject, 'b', 2)
    this.$set(this.someObject,'b',2)
    
  • 使用 Object.assign()_.extend() 等方法添加到對(duì)象上的新屬性不會(huì)觸發(fā)更新。

  • 可以創(chuàng)建一個(gè)新的對(duì)象,讓它包含原對(duì)象的屬性和新的屬性:

    // 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
    this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
    

聲明響應(yīng)式屬性

  • 必須在初始化實(shí)例前聲明根級(jí)響應(yīng)式屬性,哪怕只是一個(gè)空值。

異步更新隊(duì)列

  • Vue 異步執(zhí)行 DOM 更新。

  • 為了在數(shù)據(jù)變化之后等待 Vue 完成更新 DOM ,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback) 。這樣回調(diào)函數(shù)在 DOM 更新完成后就會(huì)調(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() 實(shí)例方法特別方便,因?yàn)樗恍枰?Vue ,并且回調(diào)函數(shù)中的 this 將自動(dòng)綁定到當(dāng)前的 Vue 實(shí)例上。

    Vue.component('example', {
      template: '<span>{{ message }}</span>',
      data: function () {
        return {
          message: 'not updated'
        }
      },
      methods: {
        updateMessage: function () {
          this.message = 'updated'
          console.log(this.$el.textContent) // => '沒有更新'
          this.$nextTick(function () {
            console.log(this.$el.textContent) // => '更新完成'
          })
        }
      }
    })
    

過渡效果

概述

  • Vue 允許多種過渡動(dòng)畫:
    • 在 CSS 過渡和動(dòng)畫中自動(dòng)應(yīng)用 class
    • 配合使用第三方 CSS 動(dòng)畫庫
    • 在過渡鉤子函數(shù)中使用 JavaScript 直接操作 DOM
    • 可以配合使用第三方 JavaScript 動(dòng)畫庫,如 Velocity.js

單元素 / 組件的過渡

  • Vue 提供了 transition 的封裝組件。在下列情形中,可以為元素或組件添加 entering / leaving 效果:
    • v-if
    • v-show
    • 動(dòng)態(tài)組件
    • 組件根節(jié)點(diǎn)
  • 當(dāng)插入或刪除包含在 transition 組件中的元素時(shí),Vue 會(huì)進(jìn)行以下處理:
    • 自動(dòng)嗅探目標(biāo)元素是否應(yīng)用了 CSS 過渡或動(dòng)畫,如果是,在恰當(dāng)?shù)臅r(shí)機(jī)添加 / 刪除 CSS 類名。
    • 如果過渡組件提供了 JavaScript 鉤子函數(shù),這些鉤子函數(shù)會(huì)在恰當(dāng)時(shí)機(jī)被調(diào)用。
    • 如果沒有找到鉤子函數(shù)也沒有檢測(cè)到 CSS 過渡 / 動(dòng)畫,DOM 操作在下一幀中立即執(zhí)行。

過渡的 CSS 類型

  • Vue 包含 4 個(gè)( CSS ) 類名在 enter / leave 的過渡中切換。

    • v-enter:定義進(jìn)入過渡的開始狀態(tài)。在元素被插入時(shí)生效,在下一個(gè)幀移除。
    • v-enter-active:定義進(jìn)入過渡的結(jié)束狀態(tài)。在元素被插入時(shí)生效,在 transition/animation 完成之后移除。
    • v-leave:定義離開過渡的開始狀態(tài)。在離開過渡被觸發(fā)時(shí)生效,在下一個(gè)幀移除。
    • v-leave-active:定義離開過渡的結(jié)束狀態(tài)。在離開過渡被觸發(fā)時(shí)生效,在 transition/animation 完成之后移除。
  • 使用 <transition name="my-transition"> 可以重置類名的 v- 前綴。

CSS 動(dòng)畫 與 CSS 過渡

  • 在 CSS animation 中,v-enter 類名在節(jié)點(diǎn)插入 DOM 后不會(huì)立即刪除。而是在 animationend 事件觸發(fā)時(shí)刪除。

自定義過渡類名

  • <transition> 允許通過以下屬性自定義過渡類名:
    • enter-class
    • enter-active-class
    • leave-class
    • leave-active-class

同時(shí)使用 Transitions 和 Animations

Vue 為了知道過渡的完成,必須設(shè)置相應(yīng)的事件監(jiān)聽器。它可以是 transitionendanimationend ,這取決于給元素應(yīng)用的 CSS 規(guī)則。如果你使用其中任何一種,Vue 能自動(dòng)識(shí)別類型并設(shè)置監(jiān)聽。
但是,在一些場(chǎng)景中,你需要給同一個(gè)元素同時(shí)設(shè)置兩種過渡動(dòng)效,比如 animation 很快的被觸發(fā)并完成了,而 transition 效果還沒結(jié)束。在這種情況中,你就需要使用 type 特性并設(shè)置 animationtransition 來明確聲明你需要 Vue 監(jiān)聽的類型。

JavaScript 鉤子

  • 可以在 <transition> 的屬性中聲明 JavaScript 鉤子:
    • @before-enter: function (el)
    • @enter: function(el, done)
    • @after-enter: function (el)
    • @enter-cancelled: function (el)
    • @before-leave: function (el)
    • @leave: function(el, done)
    • @after-leave: function (el)
    • @leave-cancelled: function (el)
  • 鉤子函數(shù)可以結(jié)合 CSS 過渡動(dòng)畫一起使用,也可以單獨(dú)使用,但是:
    • 當(dāng)只使用 JavaScript 過渡的時(shí)候,在 enterleave 中,回調(diào)函數(shù) done 是必須的。否則它們會(huì)被同步調(diào)用,過渡會(huì)立即完成。
    • 推薦對(duì)于僅使用 JavaScript 過渡的 <transition> 添加 :css="false",Vue 會(huì)跳過 CSS 檢測(cè)。

初始渲染的過渡

可以通過 appear 特性設(shè)置節(jié)點(diǎn)在初始化渲染時(shí)的過渡。

<transition appear>
...
</transition>
  • 這里的 <transition> 同樣允許自定義 CSS 類名或 JavaScript 鉤子:

    <transition
      appear
      appear-class="custom-appear-class"
      appear-active-class="custom-appear-active-class"
    >
      <!-- ... -->
    </transition>
    <transition
      appear
      v-on:before-appear="customBeforeAppearHook"
      v-on:appear="customAppearHook"
      v-on:after-appear="customAfterAppearHook"
    >
      <!-- ... -->
    </transition>
    

多個(gè)元素的過渡

  • 對(duì)于原生標(biāo)簽,可以使用 v-if / v-else 在控制多個(gè)元素的過渡。

  • 當(dāng)有相同標(biāo)簽名的元素切換時(shí),要通過 key 特性設(shè)置唯一的值來標(biāo)記以讓 Vue 區(qū)分它們。即使在技術(shù)上沒有必要,給在 <transition> 組件中的多個(gè)元素設(shè)置 key 是一個(gè)更好的實(shí)踐。

  • 使用多個(gè) v-if 的多個(gè)元素的過渡可以重寫為綁定了動(dòng)態(tài)屬性的單個(gè)過渡元素。

    <transition>
      <button v-if="docState === 'saved'" key="saved">
        Edit
      </button>
      <button v-if="docState === 'edited'" key="edited">
        Save
      </button>
      <button v-if="docState === 'editing'" key="editing">
        Cancel
      </button>
    </transition>
    

    重寫為

    <transition>
      <button v-bind:key="docState">
        {{ buttonMessage }}
      </button>
    </transition>
    
    // ...
    computed: {
      buttonMessage: function () {
        switch (docState) {
          case 'saved': return 'Edit'
          case 'edited': return 'Save'
          case 'editing': return 'Cancel'
        }
      }
    }
    

過渡模式

  • <transition> 在使用 v-if 的多組件上作用過渡時(shí),進(jìn)入和離開會(huì)同時(shí)發(fā)生,這有可能導(dǎo)致布局錯(cuò)亂或樣式重疊。可以在過渡的時(shí)候?qū)υ厥褂媒^對(duì)定位。(可以實(shí)現(xiàn) button 滑動(dòng)切換的效果)
  • 為了解決上述問題,Vue 提供了過渡模式:
    • in-out 新元素先進(jìn)行過渡,完成之后當(dāng)前元素過渡離開。
    • out-in 當(dāng)前元素先進(jìn)行過渡,完成之后新元素過渡離開。

多個(gè)組件的過渡

  • 組件過渡不需要使用 key 特性,只需要使用 <component> 構(gòu)建的動(dòng)態(tài)組件即可。

    <transition name="component-fade" mode="out-in">
      <component v-bind:is="view"></component>
    </transition>
    

列表過渡

  • 使用 <transition-group> 渲染列表。
  • 不同于 <transition>,<transition-group> 會(huì)以一個(gè)真實(shí)的元素呈現(xiàn)。默認(rèn)為一個(gè) <span>,也可以通過 tag 屬性更換。
  • 內(nèi)部元素總是需要提供一個(gè)唯一的 key 值。

列表的進(jìn)入和離開過渡

  • <transition-group> 同樣支持自定義前綴或自定義類名,與 <transition> 完全相同。

列表的位移過渡

  • <transition-group> 組件可以改變定位。使用其 v-move 特性,它會(huì)在元素改變定位的過程中應(yīng)用??梢允褂?name 改變前綴,也可以使用 move-class 手動(dòng)指定類名。

  • Vue 使用了一個(gè)叫 FLIP 的簡(jiǎn)單的動(dòng)畫隊(duì)列,使用 transforms 將元素從之前的位置平滑過渡新的位置。

    .list-item {
      display: inline-block;
      margin-right: 10px;
    }
    .list-enter-active, .list-leave-active {
      transition: all 1s;
    }
    .list-enter, .list-leave-active {
      opacity: 0;
      transform: translateY(30px);
    }
    
  • FLIP 同樣支持多維網(wǎng)格過渡。

列表的漸進(jìn)過渡

  • 通過 data 屬性與 JavaScript 通信,可以實(shí)現(xiàn)列表的漸進(jìn)過渡。技巧是設(shè)置 delay 。(可以參考官方文檔的示例)

可復(fù)用的過渡

  • <transition> 封裝成一個(gè)組件,即可進(jìn)行復(fù)用。

動(dòng)態(tài)過渡

  • 動(dòng)態(tài)過渡最基本的例子是通過 name 特性來綁定動(dòng)態(tài)值。
  • 利用過渡事件,根據(jù)組件的狀態(tài)通過 JavaScript 過渡設(shè)置不同的過渡效果。
  • 創(chuàng)建動(dòng)態(tài)過渡的最終方案是組件通過接受 props 來動(dòng)態(tài)修改之前的過渡。

過渡狀態(tài)

  • 對(duì)于數(shù)據(jù)元素本身的動(dòng)態(tài)效果,Vue 允許結(jié)合第三方庫實(shí)現(xiàn)切換動(dòng)畫。

狀態(tài)動(dòng)畫與 watcher

  • 通過 watcher 我們能監(jiān)聽到任何屬性的數(shù)值更新,并可以獲取到更新前和更新后的兩組數(shù)值。這樣,就可以利用第三方庫,結(jié)合舊數(shù)據(jù)和新數(shù)據(jù)構(gòu)建過渡動(dòng)畫了。

動(dòng)態(tài)狀態(tài)轉(zhuǎn)換

官方示例

通過組件組織過渡

  • 將一些通用的動(dòng)畫效果提取到專用的子組件里。

Render 函數(shù)

  • 一個(gè)例子

    Vue.component('anchored-heading', {
      render: function (createElement) {
        return createElement(
          'h' + this.level,   // tag name 標(biāo)簽名稱
          this.$slots.default // 子組件中的陣列
        )
      },
      props: {
        level: {
          type: Number,
          required: true
        }
      }
    })
    
  • 需要知道當(dāng)你不使用 slot 屬性向組件中傳遞內(nèi)容時(shí), 這些子元素被存儲(chǔ)在組件實(shí)例中的 $slots.default中。詳見 instance properties API

createElement 參數(shù)

  • createElement 接受的參數(shù):

    // @returns {VNode}
    createElement(
      // {String | Object | Function}
      // 一個(gè) HTML 標(biāo)簽字符串,組件選項(xiàng)對(duì)象,或者一個(gè)返回值類型為String/Object的函數(shù),必要參數(shù)
      'div',
      // {Object}
      // 一個(gè)包含模板相關(guān)屬性的數(shù)據(jù)對(duì)象
      // 這樣,您可以在 template 中使用這些屬性.可選參數(shù).
      {
        // (詳情見下一節(jié))
      },
      // {String | Array}
      // 子節(jié)點(diǎn)(VNodes),可以是一個(gè)字符串或者一個(gè)數(shù)組. 可選參數(shù).
      [
        createElement('h1', 'hello world'),
        createElement(MyComponent, {
          props: {
            someProp: 'foo'
          }
        }),
        'bar'
      ]
    )
    

    createElement 的第二個(gè)參數(shù)是可選參數(shù),且必須接收一個(gè) Object 。

深入 data object 對(duì)象

  • 在 VNode 數(shù)據(jù)對(duì)象中,下列屬性名是級(jí)別最高的字段。

    {
      // 和`v-bind:class`一樣的 API
      'class': {
        foo: true,
        bar: false
      },
      // 和`v-bind:style`一樣的 API
      style: {
        color: 'red',
        fontSize: '14px'
      },
      // 正常的 HTML 特性
      attrs: {
        id: 'foo'
      },
      // 組件 props
      props: {
        myProp: 'bar'
      },
      // DOM 屬性
      domProps: {
        innerHTML: 'baz'
      },
      // 事件監(jiān)聽器基于 "on"
      // 所以不再支持如 v-on:keyup.enter 修飾器
      // 需要手動(dòng)匹配 keyCode。
      on: {
        click: this.clickHandler
      },
      // 僅對(duì)于組件,用于監(jiān)聽原生事件,而不是組件內(nèi)部使用 vm.$emit 觸發(fā)的事件。
      nativeOn: {
        click: this.nativeClickHandler
      },
      // 自定義指令. 注意事項(xiàng):不能對(duì)綁定的舊值設(shè)值
      // Vue 會(huì)為您持續(xù)追蹤
      directives: [
        {
          name: 'my-custom-directive',
          value: '2'
          expression: '1 + 1',
          arg: 'foo',
          modifiers: {
            bar: true
          }
        }
      ],
      // Scoped slots in the form of
      // { name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: props => h('span', props.text)
      },
      // 如果組件是其他組件的子組件,需為slot指定名稱
      slot: 'name-of-slot'
      // 其他特殊頂層屬性
      key: 'myKey',
      ref: 'myRef'
    }
    

約束

  • VNode 必須唯一:組件樹中的所有 VNodes 必須是唯一的。下面的 render function 無效

    render: function (createElement) {
      var myParagraphVNode = createElement('p', 'hi')
      return createElement('div', [
        // 錯(cuò)誤-重復(fù)的VNodes
        myParagraphVNode, myParagraphVNode
      ])
    }
    
  • 使用工廠函數(shù)來實(shí)現(xiàn)重復(fù):

    render: function (createElement) {
      return createElement('div',
        Array.apply(null, { length: 20 }).map(function () {
          return createElement('p', 'hi')
        })
      )
    }
    

使用 JavaScript 代替模板功能

v-if and v-for

  • 在 render 函數(shù)中使用 JavaScript 的 if/else 和 map 重寫 v-ifv-for

    render: function (createElement) {
      if (this.items.length) {
        return createElement('ul', this.items.map(function (item) {
          return createElement('li', item.name)
        }))
      } else {
        return createElement('p', 'No items found.')
      }
    }
    

v-model

  • render 函數(shù)中沒有與 v-model 相應(yīng)的 API - 你必須自己來實(shí)現(xiàn)相應(yīng)的邏輯

    render: function (createElement) {
      var self = this
      return createElement('input', {
        domProps: {
          value: self.value
        },
        on: {
          input: function (event) {
            self.value = event.target.value
          }
        }
      })
    }
    

事件 & 按鍵修飾符

  • 一個(gè)例子:

    on: {
      '!click': this.doThisInCapturingMode,
      '~keyup': this.doThisOnce,
      `~!mouseover`: this.doThisOnceInCapturingMode
    }
    
  • 詳見事件 & 按鍵修飾符

slots

  • 可以從 this.$slots 獲取 VNodes 列表中的靜態(tài)內(nèi)容:

    render: function (createElement) {
      // <div><slot></slot></div>
      return createElement('div', this.$slots.default)
    }
    
  • 然后從 this.$scopedSlots 中以返回 VNodes 的函數(shù)返回值的形式訪問局部 slots 。

    render: function (createElement) {
      // <div><slot :text="msg"></slot></div>
      return createElement('div', [
        this.$scopedSlots.default({
          text: this.msg
        })
      ])
    }
    
  • 在 render function 中,使用 VNode data 中的 scopedSlots 域向子組件傳遞傳遞局部 slots 。

    render (createElement) {
      return createElement('div', [
        createElement('child', {
          // pass scopedSlots in the data object
          // in the form of { name: props => VNode | Array<VNode> }
          scopedSlots: {
            default: function (props) {
              return createElement('span', props.text)
            }
          }
        })
      ])
    }
    

JSX

  • Babel plugin 插件,用于在 Vue 中使用 JSX 語法。

    import AnchoredHeading from './AnchoredHeading.vue'
    new Vue({
      el: '#demo',
      render (h) {
        return (
          <AnchoredHeading level={1}>
            <span>Hello</span> world!
          </AnchoredHeading>
        )
      }
    })
    

    h 作為 createElement 的別名是 Vue 生態(tài)系統(tǒng)中的一個(gè)通用慣例,實(shí)際上也是 JSX 所要求的,如果在作用域中 h 失去作用, 在應(yīng)用中會(huì)觸發(fā)報(bào)錯(cuò)。

函數(shù)化組件

  • 一個(gè)例子

    Vue.component('my-component', {
      functional: true,
      // 為了彌補(bǔ)缺少的實(shí)例
      // 提供第二個(gè)參數(shù)作為上下文
      render: function (createElement, context) {
        // ...
      },
      // Props 可選
      props: {
        // ...
      }
    })
    
  • 詳見函數(shù)化組件

slots()children 對(duì)比

  • 對(duì)于下面這個(gè)組件:

    <my-functional-component>
      <p slot="foo">
        first
      </p>
      <p>second</p>
    </my-functional-component>
    
    • children 會(huì)給你兩個(gè)段落標(biāo)簽,而 slots().default 只會(huì)傳遞第二個(gè)匿名段落標(biāo)簽,slots().foo 會(huì)傳遞第一個(gè)具名段落標(biāo)簽。
  • 可以選擇讓組件通過 slot() 系統(tǒng)分發(fā)或者簡(jiǎn)單的通過 children 接收,讓其他組件去處理。

自定義指令

基礎(chǔ)

  • Vue.js 允許用戶通過 Vue.directive(id, definition) 方法注冊(cè)一個(gè)全局自定義指令,接收兩個(gè)參數(shù):指令 ID 和 定義對(duì)象。也可以用組件的 directive 選項(xiàng)注冊(cè)一個(gè)局部自定義指令。

    // 注冊(cè)一個(gè)全局自定義指令 v-focus
    Vue.directive('focus', {
      // 當(dāng)綁定元素插入到 DOM 中。
      inserted: function (el) {
        // 聚焦元素
        el.focus()
      }
    })
    
    directives: {
      focus: {
        // 指令的定義---
      }
    }
    

    使用:

    <input v-focus>
    

鉤子函數(shù)

  • 指令定義函數(shù)提供了幾個(gè)鉤子函數(shù)(可選):
    • bind : 只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用,用這個(gè)鉤子函數(shù)可以定義一個(gè)在綁定時(shí)執(zhí)行一次的初始化動(dòng)作。
    • inserted : 被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用(父節(jié)點(diǎn)存在即可調(diào)用,不必存在于 document 中)。
    • update : 被綁定元素所在的模板更新時(shí)調(diào)用,而不論綁定值是否變化。通過比較更新前后的綁定值,可以忽略不必要的模板更新(詳細(xì)的鉤子函數(shù)參數(shù)見下)。
    • componentUpdated : 被綁定元素所在模板完成一次更新周期時(shí)調(diào)用。
    • unbind : 只調(diào)用一次, 指令與元素解綁時(shí)調(diào)用。

鉤子函數(shù)參數(shù)

  • 鉤子函數(shù)被賦予了以下參數(shù):
    • el : 指令所綁定的元素,可以用來直接操作 DOM 。

    • binding : 一個(gè)對(duì)象,包含以下屬性:

      • name : 指令名,不包括 v- 前綴。
      • value : 指令的綁定值, 例如: v-my-directive="1 + 1" ,value 的值是 2
      • oldValue : 指令綁定的前一個(gè)值,僅在 updatecomponentUpdated 鉤子中可用。無論值是否改變都可用。
      • expression : 綁定值的字符串形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1" 。
      • arg : 傳給指令的參數(shù)。例如 v-my-directive:foo ,arg 的值是 "foo" 。
      • modifiers : 一個(gè)包含修飾符的對(duì)象。 例如: v-my-directive.foo.bar ,修飾符對(duì)象 modifiers 的值是 { foo: true, bar: true }
    • vnode : Vue 編譯生成的虛擬節(jié)點(diǎn),查閱 VNode API 了解更多詳情。

    • oldVnode : 上一個(gè)虛擬節(jié)點(diǎn),僅在 updatecomponentUpdated 鉤子中可用。

    • 除了 el 之外,其它參數(shù)都應(yīng)該是只讀的,盡量不要修改他們。如果需要在鉤子之間共享數(shù)據(jù),建議通過元素的 dataset 來進(jìn)行。

    • 一個(gè)例子:

      <div id="hook-arguments-example" v-demo:hello.a.b="message"></div>
      
      Vue.directive('demo', {
        bind: function (el, binding, vnode) {
          var s = JSON.stringify
          el.innerHTML =
            'name: '       + s(binding.name) + '<br>' +
            'value: '      + s(binding.value) + '<br>' +
            'expression: ' + s(binding.expression) + '<br>' +
            'argument: '   + s(binding.arg) + '<br>' +
            'modifiers: '  + s(binding.modifiers) + '<br>' +
            'vnode keys: ' + Object.keys(vnode).join(', ')
        }
      })
      new Vue({
        el: '#hook-arguments-example',
        data: {
          message: 'hello!'
        }
      })
      

函數(shù)簡(jiǎn)寫

  • 大多數(shù)情況下,我們可能想在 bindupdate 鉤子上做重復(fù)動(dòng)作,并且不想關(guān)心其它的鉤子函數(shù)??梢赃@樣寫:

    Vue.directive('color-swatch', function (el, binding) {
      el.style.backgroundColor = binding.value
    })
    

對(duì)象字面量

  • 如果指令需要多個(gè)值,可以傳入一個(gè) JavaScript 對(duì)象字面量。指令函數(shù)能夠接受所有合法類型的 JavaScript 表達(dá)式。

    <div v-demo="{ color: 'white', text: 'hello!' }"></div>
    
    Vue.directive('demo', function (el, binding) {
      console.log(binding.value.color) // => "white"
      console.log(binding.value.text)  // => "hello!"
    })
    

混合

基礎(chǔ)

  • 混合以一種靈活的方式為組件提供分布復(fù)用功能?;旌蠈?duì)象可以包含任意的組件選項(xiàng)。當(dāng)組件使用了混合對(duì)象時(shí),混合對(duì)象的所有選項(xiàng)將被“混入”組件自己的選項(xiàng)中。

    // 定義一個(gè)混合對(duì)象
    var myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin!')
        }
      }
    }
    // 定義一個(gè)組件,使用這個(gè)混合對(duì)象
    var Component = Vue.extend({
      mixins: [myMixin]
    })
    var component = new Component() // -> "hello from mixin!"
    

選項(xiàng)合并

  • 當(dāng)混合對(duì)象與組件包含同名選項(xiàng)時(shí),這些選項(xiàng)將以適當(dāng)?shù)牟呗?/strong>合并。
  • 混合的鉤子將在組件自己的鉤子之前調(diào)用。
  • 注意 Vue.extend() 使用同樣的合并策略。
  • 可以參考這里

全局混合

  • 一旦使用全局混合對(duì)象,將會(huì)影響到所有之后創(chuàng)建的 Vue 實(shí)例。使用恰當(dāng)時(shí),可以為自定義對(duì)象注入處理邏輯。但是通常不建議使用。語法如下:

    // 為自定義的選項(xiàng) 'myOption' 注入一個(gè)處理器。 
    Vue.mixin({
      created: function () {
        var myOption = this.$options.myOption
        if (myOption) {
          console.log(myOption)
        }
      }
    })
    new Vue({
      myOption: 'hello!'
    })
    // -> "hello!"
    

自定義選項(xiàng)混合策略

單文件組件

生產(chǎn)環(huán)境部署

單元測(cè)試

[整理中][官方文檔]

服務(wù)端渲染

[整理中][官方文檔]

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容