VUE2.0學習筆記

1.前言

安裝

  • 直接用 <script> 引入(本地或者cdn)
  • npm npm install vue
  • vue-cli官方腳手架
# 全局安裝 vue-cli
$ npm install --global vue-cli
# 創建一個基於 webpack 模板的新項目
$ vue init webpack my-project
# 安裝依賴,走你
$ cd my-project
$ npm install
$ npm run dev

簡介

Vue (讀音 /vjuː/,類似於 view) 是一套用於構建用戶界面的漸進式框架。Vue 的核心庫只關注視圖層,對應view。

Vue數據驅動,jQuery是結構驅動

原理

內部使用Object.defineProperty(最低支持IE9)把所有屬性全部轉爲 getter/setter,爲每個組件綁定了watcher 實例對象,並且把屬性作爲依賴項,當依賴項的setter調用時,watcher將會重新計算,從而更新組件。

  • [組件render]-<創建>-[getter、setter]-<收集依賴>-[watcher]
  • [觸發setter]-<通知>-[watcher]-<觸發>-[組件渲染函數]-<更新>-[組件]

image

image.png)

開發環境

  • vueTools
  • vscode【Vetur、Vue2 Snippets】
  • weboack

2.實例

聲明式渲染

<!--html-->
<div id="app">
  {{ message }}
</div>
//js
var vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

數據與方法

當一個 Vue 實例被創建時,它向 Vue 的響應式系統中加入了其 data 對象中能找到的所有的屬性。當這些屬性的值發生改變時,視圖將會產生“響應”,即匹配更新爲新的值。

// 我們的數據對象
var data = { a: 1 }

// 該對象被加入到一個 Vue 實例中
var vm = new Vue({
  data: data
})

// 他們引用相同的對象!
vm.a === data.a // => true

// 設置屬性也會影響到原始數據
vm.a = 2
data.a // => 2

// ... 反之亦然
data.a = 3
vm.a // => 3

當這些數據改變時,視圖會進行重渲染。值得注意的是只有當實例被創建時 data 中存在的屬性是響應式的。也就是說如果你添加一個新的屬性,將不會觸發任何視圖的更新。如果你知道你會在晚些時候需要一個屬性,但是一開始它爲空或不存在,那麼你僅需要設置一些初始值。

var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

// $watch 是一個實例方法
vm.$watch('a', function (newValue, oldValue) {
  // 這個回調將在 `vm.a` 改變後調用
})

自身屬性和方法

vue實例自身暴露的屬性和方法通過前綴$來獲取

var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data // => true
vm.$el === document.getElementById('example') // => true

實例生命週期

每個 Vue 實例在被創建之前都要經過一系列的初始化過程(生命週期)。在這個過程中會運行一些叫做生命週期鉤子的函數,用戶可以在不同階段添加自己的代碼來做一些事情。
image

  • beforeCreate:在實例初始化之後,數據觀測 (data observer) 和 event/watcher 事件配置之前被調用。
  • created:在實例創建完成後被立即調用。在這一步,實例已完成以下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見。
  • beforeMount:在掛載開始之前被調用:相關的 render 函數首次被調用。
  • mounted:el 被新創建的 vm.$el 替換,並掛載到實例上去之後調用該鉤子。
  • beforeUpdate:數據更新時調用,發生在虛擬 DOM 重新渲染和打補丁之前。
  • updated:由於數據更改導致的虛擬 DOM 重新渲染和打補丁,在這之後會調用該鉤子
  • beforeDestroy:實例銷燬之前調用。在這一步,實例仍然完全可用。
  • destroyed:Vue 實例銷燬後調用。調用後,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷燬。
  • activated/deactivated:keep-alive 組件激活/停用時調用,
  • errorCaptured:當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子可以返回 false 以阻止該錯誤繼續向上傳播。

注意:

  • beforeCreate,created外的鉤子在服務器端渲染期間不被調用。
  • 不要在選項屬性或回調上使用箭頭函數,比如
//錯誤,會導致this不會指向Vue 實例
created: () => console.log(this.a)
vm.$watch('a', newValue => this.myMethod())

Vue對象的選項

var vm = new Vue({
  // 數據
  data: "聲明需要響應式綁定的數據對象",
  props: "接收來自父組件的數據",
  propsData: "創建實例時手動傳遞props,方便測試props",
  computed: "計算屬性",
  methods: "定義可以通過vm對象訪問的方法",
  watch: "Vue實例化時會調用$watch()方法遍歷watch對象的每個屬性",
  // DOM
  el: "將頁面上已存在的DOM元素作爲Vue實例的掛載目標",
  template: "可以替換掛載元素的字符串模板",
  render: "渲染函數,字符串模板的替代方案",
  renderError: "僅用於開發環境,在render()出現錯誤時,提供另外的渲染輸出",
  // 生命週期鉤子
  beforeCreate: "發生在Vue實例初始化之後,data observer和event/watcher事件被配置之前",
  created: "發生在Vue實例初始化以及data observer和event/watcher事件被配置之後",
  beforeMount: "掛載開始之前被調用,此時render()首次被調用",
  mounted: "el被新建的vm.$el替換,並掛載到實例上之後調用",
  beforeUpdate: "數據更新時調用,發生在虛擬DOM重新渲染和打補丁之前",
  updated: "數據更改導致虛擬DOM重新渲染和打補丁之後被調用",
  activated: "keep-alive組件激活時調用",
  deactivated: "keep-alive組件停用時調用",
  beforeDestroy: "實例銷燬之前調用,Vue實例依然可用",
  destroyed: "Vue實例銷燬後調用,事件監聽和子實例全部被移除,釋放系統資源",
  // 資源
  directives: "包含Vue實例可用指令的哈希表",
  filters: "包含Vue實例可用過濾器的哈希表",
  components: "包含Vue實例可用組件的哈希表",
  // 組合
  parent: "指定當前實例的父實例,子實例用this.$parent訪問父實例,父實例通過$children數組訪問子實例",
  mixins: "將屬性混入Vue實例對象,並在Vue自身實例對象的屬性被調用之前得到執行",
  extends: "用於聲明繼承另一個組件,從而無需使用Vue.extend,便於擴展單文件組件",
  provide&inject: "2個屬性需要一起使用,用來向所有子組件注入依賴,類似於React的Context",
  // 其它
  name: "允許組件遞歸調用自身,便於調試時顯示更加友好的警告信息",
  delimiters: "改變模板字符串的風格,默認爲{{}}",
  functional: "讓組件無狀態(沒有data)和無實例(沒有this上下文)",
  model: "允許自定義組件使用v-model時定製prop和event",
  inheritAttrs: "默認情況下,父作用域的非props屬性綁定會應用在子組件的根元素上。當編寫嵌套有其它組件或元素的組件時,可以將該屬性設置爲false關閉這些默認行爲",
  comments: "設爲true時會保留並且渲染模板中的HTML註釋"
});

3.模板語法

Vue.js 使用了基於 HTML 的模板語法,必須是合法的 HTML。在底層的實現上,Vue 將模板編譯成虛擬 DOM 渲染函數。

插值

文本

<!--Mustache-->
<span>Message: {{ msg }}</span>
<!--v-text-->
<span v-text="msg"></span>
<!--v-once:一次性插值-->
<span v-once>這個將不會改變: {{ msg }}</span>

HTML

<p>Using v-html directive: <span v-html="rawHtml"></span></p>

只對可信內容使用 HTML 插值,絕不要對用戶提供的內容使用插值。

特性

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

在插值中可以使用表達式,但只限簡單表達式。

{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>

指令

指令 (Directives) 是帶有 v- 前綴的特殊屬性。
指令的職責是,當表達式的值改變時,將其產生的連帶影響,響應式地作用於 DOM。

<p v-if="seen">現在你看到我了</p>
<a v-on:click="doSomething">...</a>
指令 預期/限制 作用
v-text string 文本插值
v-html string html插值
v-show any 條件顯示
v-if、v-else、v-else-if any 條件渲染
v-for Array/Object/number/string 列表渲染
v-on(@) Function/Inline Statement/Object 事件綁定
v-bind(:) any (with argument)/Object (without argument) 特性綁定
v-model 僅限<input>/<select>/<textarea>/components元素使用 雙向綁定
v-pre 忽略編譯
v-cloak 避免顯示Mustache
v-once 一次性渲染

修飾符

修飾符 (Modifiers) 是以半角句號 . 指明的特殊後綴,用於指出一個指令應該以特殊方式綁定。

<form v-on:submit.prevent="onSubmit">...</form>
  • v-on能使用的修飾符:
修飾符 作用
.stop 調用 event.stopPropagation()。
.prevent 調用 event.preventDefault()。
.capture 添加事件偵聽器時使用 capture 模式。
.self 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
.{keyCode / keyAlias} 只當事件是從特定鍵觸發時才觸發回調。
.native 監聽組件根元素的原生事件。
.once 只觸發一次回調。
.left (2.2.0) 只當點擊鼠標左鍵時觸發。
.right (2.2.0) 只當點擊鼠標右鍵時觸發。
.middle (2.2.0) 只當點擊鼠標中鍵時觸發。
.passive (2.3.0) 以 { passive: true } 模式添加偵聽器
  • v-bind能使用的修飾符:
修飾符 作用
.prop 被用於綁定 DOM 屬性 (property)。(差別在哪裏?)
.camel (2.1.0+) 將 kebab-case 特性名轉換爲 camelCase. (從 2.1.0 開始支持)
.sync (2.3.0+) 語法糖,會擴展成一個更新父組件綁定值的 v-on 偵聽器。
  • v-model能使用的修飾符:
修飾符 作用
.lazy 取代 input 監聽 change 事件
.number 輸入字符串轉爲數字
.trim 輸入首尾空格過濾

4.計算屬性和觀察者

計算屬性

對於任何複雜邏輯,你都應當使用計算屬性,而不應直接放在模板中。

計算屬性也是響應式的,但是它會基於它們的依賴進行緩存的,只有當緩存改變,它纔會重新求值;否則會直接返回緩存的結果,而不必再次執行函數。

應當優先使用計算屬性而不是偵聽屬性。

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 計算屬性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 實例
      return this.message.split('').reverse().join('')
    }
  }
})

下面的計算屬性不會更新,因爲Date.now() 不是響應式依賴。

computed: {
  now: function () {
    return Date.now()
  }
}

計算屬性緩存 vs 方法

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

方法在每次調用時總會再次執行函數

setter

計算屬性默認只有 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]
    }
  }
}

偵聽器

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
watch: {
    // 如果 `question` 發生改變,這個函數就會運行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.getAnswer()
    }
  }

銷燬

// const unWatch = app.$watch('text', (newText, oldText) => {
//   console.log(`${newText} : ${oldText}`)
// })
// setTimeout(() => {
//   unWatch()
// }, 2000)

5. Class與Style綁定

Class

對象語法

當value爲真時,綁定對應的key到class

<!--內聯在模板中-->
<div class="static"
     v-bind:class="{ active: isActive, 'text-danger': hasError }">
</div>
<!--綁定data或者計算屬性的的一個對象-->
<div v-bind:class="classObject"></div>
<!--js-->
data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}

數組語法

<!--模板-->
<div v-bind:class="[activeClass, errorClass]"></div>
<!--js-->
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}
<!--結果-->
<div class="active text-danger"></div>

也可以使用三元表達式。

// isActive爲真添加activeClass,errorClass始終存在
<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>

混合

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

用在組件上

class將被添加到該組件的根元素上面。該元素上已經存在的class不會被覆蓋。

<my-component class="baz boo"></my-component>

注意:和普通的class並存,並不會覆蓋(不同名),最終會合成一個class。

Style

自動偵測並添加相應瀏覽器引擎前綴。

對象語法

CSS 屬性名可以用駝峯式 (camelCase) 或短橫線分隔 (kebab-case,記得用單引號括起來) 來命名。

<!--內聯在模板中-->
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<!--js-->
data: {
  activeColor: 'red',
  fontSize: 30
}
<!--綁定data或者計算屬性的的一個對象-->
<div v-bind:style="styleObject"></div>
<!--js-->
data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

數組語法

可以將多個樣式對象應用到同一個元素上

<div v-bind:style="[baseStyles, overridingStyles]"></div>

多重值

<!--常用於提供多個帶前綴的值-->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>

6.條件渲染

v-if(v-else、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>

如果需要條件渲染多個元素,可以使用<template>包裹。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>
key

Vue 會儘可能高效地渲染元素,通常會複用已有元素而不是從頭開始渲染。添加一個具有唯一值的 key 屬性可以強制其重新渲染。

v-show

根據表達式之真假值,切換元素的 display CSS 屬性。

<h1 v-show="ok">Hello!</h1>

7.列表渲染

數組

<!--普通-->
<ul id="example">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
<!--帶索引-->
<ul id="example">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
<!--js-->
var example = new Vue({
  el: '#example',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

數組更新檢測

包含變異(改變原數組)和非變異(生成新數組,不改變原數組)兩組方式,都將觸發更新。

  • 變異方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
  • 非變異方法(需用新數組替換原數組):filter()、concat()、slice()

不能檢測的變動:

  • 當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
  • 當你修改數組的長度時,例如:vm.items.length = newLength

對象

<!--普通-->
<li v-for="value in object">
{{ value }}
</li>
<!--帶key-->
<div v-for="(value, key) in object">
  {{ key }}: {{ value }}
</div>
<!--帶key、索引-->
<div v-for="(value, key, index) in object">
  {{ index }}. {{ key }}: {{ value }}
</div>
<!--js-->
new Vue({
  data: {
    object: {
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }
  }
})

對象更改檢測注意事項

Vue 不能檢測對象屬性的添加或刪除。

  • 對於已經創建的實例,Vue 不能動態添加根級別的響應式屬性。
var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 現在是響應式的

vm.b = 2
// `vm.b` 不是響應式的
  • 可以使用 Vue.set(object, key, value) 方法向嵌套對象添加響應式屬性
var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})
vm.$set(this.userProfile, 'age', 27)
  • 多個屬性可以使用Object.assign() 或 _.extend()
this.userProfile = Object.assign({}, this.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

delete

對應的刪除屬性使用vm.$delete(obj,key)

key

當 Vue.js 用 v-for 正在更新已渲染過的元素列表時,它默認用“就地複用”策略。
建議儘可能在使用 v-for 時爲每一項提供一個唯一的 key。
循環組件的時候,key是必須的。

<div v-for="item in items" :key="item.id">
  <!-- 內容 -->
</div>

其他

  • v-for的循環對象也可以是計算屬性和帶返回值的method 方法。
  • 利用帶有 v-for 的 <template> 渲染多個元素
<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider"></li>
  </template>
</ul>
  • v-for和v-if處於同一節點時,v-for 具有比 v-if 更高的優先級

8.事件處理

事件

<div id="example-3">
  <button v-on:click="say('hi')">Say hi</button>
  <!--訪問原始的 DOM 事件-->
  <button v-on:click="say2('what', $event)">Say what</button>
</div>
new Vue({
  el: '#example-3',
  methods: {
    say: function (message) {
      alert(message)
    },
    say2: function (message,event) {
        // 現在我們可以訪問原生事件對象
        if (event) event.preventDefault()
      alert(message)
    }
  }
})

事件修飾符

修飾符可以串聯,代碼會以串聯的順序產生。

修飾符 作用
.stop 調用 event.stopPropagation()。
.prevent 調用 event.preventDefault()。
.capture 添加事件偵聽器時使用 capture 模式。
.self 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。
.once 只觸發一次回調。
<!-- 阻止單擊事件繼續傳播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重載頁面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修飾符可以串聯 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修飾符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件監聽器時使用事件捕獲模式 -->
<!-- 即元素自身觸發的事件先在此處處理,然後才交由內部元素進行處理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只當在 event.target 是當前元素自身時觸發處理函數 -->
<!-- 即事件不是從內部元素觸發的 -->
<div v-on:click.self="doThat">...</div>
<!-- 點擊事件將只會觸發一次(可用於自定義組件) -->
<a v-on:click.once="doThis"></a>

Vue 還對應 addEventListener 中的 passive 選項提供了 .passive 修飾符,能夠提升移動端的性能,但是要避免和.prevent一起使用。

<!-- 滾動事件的默認行爲 (即滾動行爲) 將會立即觸發 -->
<!-- 而不會等待 `onScroll` 完成  -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div v-on:scroll.passive="onScroll">...</div>

按鍵修飾符

在監聽鍵盤事件時,我們經常需要檢查常見的鍵值。Vue 允許爲 v-on 在監聽鍵盤事件時添加按鍵修飾符。

<!-- 只有在 `keyCode` 是 13 時調用 `vm.submit()` -->
<input v-on:keyup.13="submit">
<!-- 縮寫語法 -->
<input @keyup.enter="submit">
  • 全部的按鍵別名:.enter.tab.delete (捕獲“刪除”和“退格”鍵)、.esc.space.up.down.left.right
  • 自定義按鍵修飾符別名
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
  • 自動匹配按鍵修飾符
<!--可直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉換爲 kebab-case 來作爲修飾符:-->
<input @keyup.page-down="onPageDown">

系統修飾鍵

.ctrl.alt.shift.meta
在和 keyup 事件一起用時,事件觸發時修飾鍵必須處於按下狀態。換句話說,只有在按住 ctrl 的情況下釋放其它按鍵,才能觸發 keyup.ctrl。而單單釋放 ctrl 也不會觸發事件。

<!-- Alt + C -->
<input @keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

.exact 修飾符允許你控制由精確的系統修飾符組合觸發的事件。

<!-- 即使 Alt 或 Shift 被一同按下時也會觸發 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的時候才觸發 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 沒有任何系統修飾符被按下的時候才觸發 -->
<button @click.exact="onClick">A</button>

鼠標按鈕修飾符

.left.right.middle
僅響應特定的鼠標按鈕

9.表單輸入綁定

基礎用法

可以用 v-model 指令在表單 <input><textarea> 元素上創建雙向數據綁定。

v-model僅爲v-on:inputv-bind:value語法糖而已。

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

注意:v-model 會忽略所有表單元素的 value、checked、selected 特性的初始值而總是將 Vue 實例的數據作爲數據來源。你應該通過 JavaScript 在組件的 data 選項中聲明初始值。

  • 文本/多行文本
<input v-model="message" placeholder="edit me">
<textarea v-model="message" placeholder="add multiple lines"></textarea>
<p>Message is: {{ message }}</p>
  • 複選框
<div id='example-3'>
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mike</label>
  <br>
  <span>Checked names: {{ checkedNames }}</span>
</div>
new Vue({
  el: '#example-3',
  data: {
    checkedNames: []
  }
})
//Checked names: [ "Jack", "John", "Mike" ]
  • 單選按鈕
<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>
new Vue({
  el: '#example-4',
  data: {
    picked: ''
  }
})
//Picked: Two
  • 選擇框
<div id="example-5">
  <select v-model="selected">
    <option disabled value="">請選擇</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '...',
  data: {
    selected: ''
  }
})
//Selected: B

爲多選時則返回一個數組Selected: [ "A", "B" ]

值綁定

  • 複選框
<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no"
>
  • 單選按鈕
<input type="radio" v-model="pick" v-bind:value="a">
  • 選擇框的選項
<select v-model="selected">
    <!-- 內聯對象字面量 -->
  <option v-bind:value="{ number: 123 }">123</option>
</select>

修飾符

  • .lazy,默認input事件觸發,使用此修飾則改爲change事件觸發
<!-- 在“change”時而非“input”時更新 -->
<input v-model.lazy="msg" >
  • .number將輸入的值轉換爲數值
  • .trim過濾掉輸入內容的首尾空白字符

10.組件

簡介

image
組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。組件是具有特殊功能的自定義元素。

所有的 Vue 組件同時也都是 Vue 的實例,所以可接受相同的選項對象 (除了一些根級特有的選項) 並提供相同的生命週期鉤子。

註冊組件

全局組件

<div id="example">
  <my-component></my-component>
</div>
//注意確保在初始化根實例之前註冊組件
// 註冊
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})

局部組件

var Child = {
  template: '<div>A custom component!</div>'
}

new Vue({
  // ...
  components: {
    // <my-component> 將只在父組件模板中可用
    'my-component': Child
  }
})

自動註冊

webpack 的 vue cli3+

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.replace(/^\.\/(.*)\.\w+$/, '$1')
    )
  )

  // 全局註冊組件
  Vue.component(
    componentName,
    // 如果這個組件選項是通過 `export default` 導出的,
    // 那麼就會優先使用 `.default`,
    // 否則回退到使用模塊的根。
    componentConfig.default || componentConfig
  )
})
注意
  • data必須是帶return的函數
  • 如果將組件用於像 <ul><ol><table><select> 這樣的元素裏面,爲了遵循規範,應該使用is:
<table>
  <tr is="my-row"></tr>
</table>

以下類型模板無此限制:<script type="text/x-template">、JavaScript 內聯模板字符串、.vue 組件

單文件組件

可以包含<template><script><style><docs>四個元素。

  • <template>內只允許有一個根元素
  • <style>可以有多個
  • <docs>說明文檔
  • <script><style>支持src導入

組件通信

image

Prop

父組件向子組件傳遞數據。

Vue.component('child', {
  // 聲明 props
  props: ['message'],
  // 就像 data 一樣,prop 也可以在模板中使用
  // 同樣也可以在 vm 實例中通過 this.message 來使用
  template: '<span>{{ message }}</span>'
})
<child message="hello!"></child>
動態 Prop:
<child v-bind:my-message="parentMsg"></child>

如果你想把一個對象的所有屬性作爲 prop 進行傳遞,可以使用不帶任何參數的 v-bind

todo: {
  text: 'Learn Vue',
  isComplete: false
}
<todo-item v-bind="todo"></todo-item>
//等價於
<todo-item
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>
字面量語法 vs 動態語法
<!-- 傳遞了一個字符串 "1" -->
<comp some-prop="1"></comp>
<!-- 傳遞真正的數值 -->
<comp v-bind:some-prop="1"></comp>
驗證

爲組件的 prop 指定驗證規則,會在組件實例創建之前進行校驗。如果傳入的數據不符合要求,Vue 會發出警告。

Vue.component('example', {
  props: {
    // 基礎類型檢測 (`null` 指允許任何類型)
    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 value > 10
      }
    }
  }
})

type 可以是下面原生構造器StringNumberBooleanFunctionObjectArraySymbol

組件可以接收任意傳入的特性,這些特性都會被添加到組件的根元素上,且會做合併處理。

自定義事件

子組件向父組件傳遞數據。

  • 使用 $on(eventName) 監聽事件
  • 使用 $emit(eventName) 觸發事件
<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="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

父子雙向通信

  • .sync

@update的語法糖

<comp :foo.sync="bar"></comp>
this.$emit('update:foo', newValue)

等價於

<comp :foo="bar" @update:foo="val => bar = val"></comp>
this.$emit('update:foo', newValue)
  • v-model(僅適用於表單輸入組件)

v-on:inputv-bind:value的語法糖

<input v-model="something">
// 通過 input 事件帶出數值
this.$emit('input', Number(formattedValue))

等價於

<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">
this.$emit('input', Number(formattedValue))

非父子組件通信

  • 簡單場景bus.js
var bus = new Vue()
// 觸發組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創建的鉤子中監聽事件
bus.$on('id-selected', function (id) {
  // ...
})

注: 還可以使用$ref、$parent、$child進行通信,不過不推薦

  • 複雜的場景請使用vuex

插槽

爲了讓組件可以組合,我們需要一種方式來混合父組件的內容與子組件自己的模板。這個過程被稱爲內容分發。
編譯作用域: 父組件模板的內容在父組件作用域內編譯;子組件模板的內容在子組件作用域內編譯。

單個插槽

除非子組件模板包含至少一個 <slot> 插口,否則父組件的內容將會被丟棄。當子組件模板只有一個沒有屬性的插槽時,父組件傳入的整個內容片段將插入到插槽所在的 DOM 位置,並替換掉插槽標籤本身。

最初在 <slot> 標籤中的任何內容都被視爲備用內容。備用內容在子組件的作用域內編譯,並且只有在宿主元素爲空,且沒有要插入的內容時才顯示備用內容。

<!--子組件-->
<div>
  <h2>我是子組件的標題</h2>
  <slot>
    只有在沒有要分發的內容時纔會顯示。
  </slot>
</div>

<!--父組件-->
<div>
  <h1>我是父組件的標題</h1>
  <my-component>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </my-component>
</div>

<!--結果-->
<div>
  <h1>我是父組件的標題</h1>
  <div>
    <h2>我是子組件的標題</h2>
    <p>這是一些初始內容</p>
    <p>這是更多的初始內容</p>
  </div>
</div>

具名插槽

<slot> 元素可以用一個特殊的特性 name 來進一步配置如何分發內容。多個插槽可以有不同的名字。具名插槽將匹配內容片段中有對應 slot 特性的元素。

仍然可以有一個匿名插槽,它是默認插槽,作爲找不到匹配的內容片段的備用插槽。如果沒有默認插槽,這些找不到匹配的內容片段將被拋棄。

<!--子組件-->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

<!--父組件-->
<app-layout>
  <h1 slot="header">這裏可能是一個頁面標題</h1>

  <p>主要內容的一個段落。</p>
  <p>另一個主要段落。</p>

  <p slot="footer">這裏有一些聯繫信息</p>
</app-layout>

<!--結果-->
<div class="container">
  <header>
    <h1>這裏可能是一個頁面標題</h1>
  </header>
  <main>
    <p>主要內容的一個段落。</p>
    <p>另一個主要段落。</p>
  </main>
  <footer>
    <p>這裏有一些聯繫信息</p>
  </footer>
</div>

作用域插槽

和普通的插槽對比,能夠傳遞數據。

<!--子組件-->
<div class="child">
  <slot text="hello from child"></slot>
</div>

<!--父組件-->
<div class="parent">
  <child>
  <!--2.5.0+,slot-scope 能被用在任意元素或組件中而不再侷限於 <template>-->
    <template slot-scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
    </template>
  </child>
</div>

<!--結果-->
<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>

動態組件

通過使用保留的 <component> 元素,並對其 is 特性進行動態綁定,你可以在同一個掛載點動態切換多個組件:

var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})

keep-alive

把切換出去的組件保留在內存中,保留其狀態或避免重新渲染

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

注意事項

  • 組件複用性,鬆耦合
  • 謹慎使用ref
  • 在大型應用中使用異步加載
  • PascalCase聲明, kebab-case使用
  • 爲遞歸組件添加name
  • 對低開銷的靜態組件使用 v-once

11.過渡和動畫

Vue 在插入、更新或者移除 DOM 時,提供多種不同方式的應用過渡效果。

單元素/組件的過渡

適用場景:條件渲染 (使用 v-if)、條件展示 (使用 v-show)、動態組件、組件根節點

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

過渡的類名

  • v-enter:定義進入過渡的開始狀態。在元素被插入時生效,在下一個幀移除。
  • v-enter-active:定義過渡的狀態。在元素整個過渡過程中作用,在元素被插入時生效,在 transition/animation 完成之後移除。這個類可以被用來定義過渡的過程時間,延遲和曲線函數。
  • v-enter-to: 2.1.8版及以上 定義進入過渡的結束狀態。在元素被插入一幀後生效 (與此同時 v-enter 被刪除),在 transition/animation 完成之後移除。
  • v-leave: 定義離開過渡的開始狀態。在離開過渡被觸發時生效,在下一個幀移除。
  • v-leave-active:定義過渡的狀態。在元素整個過渡過程中作用,在離開過渡被觸發後立即生效,在 transition/animation 完成之後移除。這個類可以被用來定義過渡的過程時間,延遲和曲線函數。
  • v-leave-to: 2.1.8版及以上 定義離開過渡的結束狀態。在離開過渡被觸發一幀後生效 (與此同時 v-leave 被刪除),在 transition/animation 完成之後移除。

image

動畫

動畫在css中使用animation即可,其他和過渡類似。

自定義過渡的類名

我們可以通過以下特性來自定義過渡類名:
enter-class、enter-active-class、enter-to-class (2.1.8+)、leave-class、leave-active-class、leave-to-class (2.1.8+)

<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>

設定持續時間

<transition :duration="1000">...</transition>
<transition :duration="{ enter: 500, leave: 800 }">...</transition>

JavaScript 鉤子

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
// ...
methods: {
  // --------
  // 進入中
  // --------

  beforeEnter: function (el) {
    // ...
  },
  // 此回調函數是可選項的設置,done 是必須的 
  // 與 CSS 結合時使用
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // 離開時
  // --------

  beforeLeave: function (el) {
    // ...
  },
  // 此回調函數是可選項的設置,done 是必須的 
  // 與 CSS 結合時使用
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  // leaveCancelled 只用於 v-show 中
  leaveCancelled: function (el) {
    // ...
  }
}

初始渲染的過渡

可以通過 appear 特性設置節點在初始渲染的過渡

<!--css-->
<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class" (2.1.8+)
  appear-active-class="custom-appear-active-class"
>
  <!-- ... -->
</transition>
<!--JS鉤子-->
<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook"
>
  <!-- ... -->
</transition>

多個元素的過渡

當有相同標籤名的元素切換時,建議給元素設置key。

過渡模式

  • in-out:新元素先進行過渡,完成之後當前元素過渡離開。
  • out-in:當前元素先進行過渡,完成之後新元素過渡進入。
<transition name="fade" mode="out-in">
  <!-- ... the buttons ... -->
</transition>

多個組件的過渡

多個組件的過渡使用動態組件

<!--html-->
<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>
<!--js-->
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})

列表過渡

使用 <transition-group> 組件。

  • 它會以一個真實元素呈現:默認爲一個 <span>。你也可以通過 tag 特性更換爲其他元素。
  • 內部元素 總是需要 提供唯一的 key 屬性值
<div id="list-demo" class="demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list" tag="p">
    <span v-for="item in items" v-bind:key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
</div>

<transition-group> 組件還有一個特殊之處。不僅可以進入和離開動畫,還可以改變定位。v-move 特性,它會在元素的改變定位的過程中應用。

<!--html-->
<transition-group name="flip-list" tag="ul">
    <li v-for="item in items" v-bind:key="item">
      {{ item }}
    </li>
</transition-group>
<!--css-->
.flip-list-move {
  transition: transform 1s;
}

也可以通過 move-class 屬性手動設置

技巧

  • 創建可複用過度組件,將 <transition> 或者 <transition-group> 作爲根組件
<transition
    name="very-special-transition"
    mode="out-in"
    v-on:before-enter="beforeEnter"
    v-on:after-enter="afterEnter">
    <slot></slot>
</transition>
  • 動態過渡,通過動態綁定name實現
<transition v-bind:name="transitionName">
  <!-- ... -->
</transition>

12.可複用性和組合

混合

混合 (mixins) 是一種分發 Vue 組件中可複用功能的非常靈活的方式。混合對象可以包含任意組件選項。當組件使用混合對象時,所有混合對象的選項將被混入該組件本身的選項。

// 定義一個混合對象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定義一個使用混合對象的組件
var Component = Vue.extend({
  mixins: [myMixin]
})
  • 當組件和混合對象含有同名選項時,這些選項將以恰當的方式混合。比如,同名鉤子函數將混合爲一個數組,因此都將被調用。另外,混合對象的 鉤子將在組件自身鉤子 之前 調用 。
  • 值爲對象的選項,例如 methods, components 和 directives,將被混合爲同一個對象。兩個對象鍵名衝突時,取組件對象的鍵值對。
  • Vue.extend() 也使用同樣的策略進行合併。

自定義指令

除了核心功能默認內置的指令 (v-model 和 v-show),Vue 也允許註冊自定義指令。

// 註冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
  // 當被綁定的元素插入到 DOM 中時……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})
// 註冊一個局部自定義指令
directives: {
  focus: {
    // 指令的定義
    inserted: function (el) {
      el.focus()
    }
  }
}
//使用
<input v-focus>

鉤子函數

一個指令定義對象可以提供如下幾個鉤子函數 (均爲可選):

  • bind:只調用一次,指令第一次綁定到元素時調用。在這裏可以進行一次性的初始化設置。
  • inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
  • update:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新後調用。
  • unbind:只調用一次,指令與元素解綁時調用。

鉤子函數參數

指令鉤子函數會被傳入以下參數:

  • el:指令所綁定的元素,可以用來直接操作 DOM 。
  • binding:一個對象,包含以下屬性:

    • name:指令名,不包括 v- 前綴。
    • value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值爲 2。
    • oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
    • expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式爲 "1 + 1"。
    • arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數爲 "foo"。
    • modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象爲 { foo: true, bar: true }。
  • vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。
  • oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。

渲染函數 & JSX

https://cn.vuejs.org/v2/guide...

渲染函數render

createElement

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一個 HTML 標籤字符串,組件選項對象,或者
  // 解析上述任何一種的一個 async 異步函數,必要參數。
  'div',

  // {Object}
  // 一個包含模板相關屬性的數據對象
  // 這樣,您可以在 template 中使用這些屬性。可選參數。
  {
    // (詳情見下面的數據對象)
  },

  // {String | Array}
  // 子節點 (VNodes),由 `createElement()` 構建而成,
  // 或使用字符串來生成“文本節點”。可選參數。
  [
    '先寫一些文字',
    createElement('h1', '一則頭條'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

數據對象:

{
  // 和`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'
  },
  // 事件監聽器基於 `on`
  // 所以不再支持如 `v-on:keyup.enter` 修飾器
  // 需要手動匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 僅對於組件,用於監聽原生事件,而不是組件內部使用
  // `vm.$emit` 觸發的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定義指令。注意,你無法對 `binding` 中的 `oldValue`
  // 賦值,因爲 Vue 已經自動爲你進行了同步。
  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 => createElement('span', props.text)
  },
  // 如果組件是其他組件的子組件,需爲插槽指定名稱
  slot: 'name-of-slot',
  // 其他特殊頂層屬性
  key: 'myKey',
  ref: 'myRef'
}

過濾器

過濾器可以用在兩個地方:雙花括號插值和 v-bind 表達式。

<!-- 在雙花括號中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
//定義局部過濾器
filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}
//定義全局過濾器
Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})
  • 過濾器可以串聯,依次執行,前面的輸出作爲後面一個的輸入。
{{ message | filterA | filterB }}
  • 過濾器可以接收參數(管道符前面的值作爲第一個參數,括號內的第一個參數爲第二個,依次類推)
{{ message | filterA('arg1', arg2) }}

13.Vue-Router

安裝

  • 直接用 <script> 引入(本地或者cdn)
  • npm npm install vue

必須要通過 Vue.use() 明確地安裝路由功能,且要通過 router 配置參數注入Vue實例,從而讓整個應用都有路由功能。

作用

將頁面組件(components)映射到路由(routes),然後告訴 vue-router 在哪裏渲染它們

router-link

  • to: 屬性指定目標地址,默認渲染成帶有正確鏈接的 標籤,
  • replace:相當於router.replace() 不會留下 history 記錄
  • append:設置 append 屬性後,則在當前(相對)路徑前添加基路徑。例如,我們從 /a 導航到一個相對路徑 b,如果沒有配置 append,則路徑爲 /b,如果配了,則爲 /a/b
  • tag: 屬性生成別的標籤.。另外,當目標路由成功激活時,鏈接元素自動設置一個表示激活的 CSS 類名。
  • active-class:鏈接激活時使用的 CSS 類名。默認值可以通過路由的構造選項 linkActiveClass 來全局配置。

    將激活 class 應用在外層元素:

<router-link tag="li" to="/foo">
  <a>/foo</a>
</router-link>

在這種情況下,<a> 將作爲真實的鏈接(它會獲得正確的 href 的),而 "激活時的CSS類名" 則設置到外層的 <li>

router-view

路由視圖容器

<transition>
<!--使用路由緩存-->
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</transition>

如果 <router-view>設置了名稱,則會渲染對應的路由配置中 components 下的相應組件。

<!--html-->
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
<!--js-->
const router = new VueRouter({
  routes: [
    {
      path: '/',
      components: {
        default: Foo,
        a: Bar,
        b: Baz
      }
    }
  ]
})

動態路由匹配

  • 動態路徑參數params
//定義
routes: [
    // 動態路徑參數 以冒號開頭
    { path: '/user/:id', component: User }
  ]
//調用
$route.params.id
  • 動態路徑查詢參數query
//定義
routes: [
    // 動態路徑參數 以冒號開頭
    { path: '/user?id=6456456', component: User }
  ]
//調用
$route.query.id
  • 區別:params定義了就是路由的一部分,就必須要傳,否則匹配失敗,query可以缺省

響應路由參數的變化

當路由參數變化時,組件的生命週期鉤子不會再被調用。
想對路由參數的變化作出響應的話,有以下兩種方式:

  • watch(監測變化) $route 對象:
const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 對路由變化作出響應...
    }
  }
}
  • 使用beforeRouteUpdate 守衛:
const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

匹配優先級

同一個路徑可以匹配多個路由時,誰先定義的,誰的優先級就最高。
因此,404類的頁面一定要放在最後,路由是按照聲明順序匹配,如果不是最後則404之後的頁面都會跳轉到404。

嵌套路由

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 當 /user/:id/profile 匹配成功,
          // UserProfile 會被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 當 /user/:id/posts 匹配成功
          // UserPosts 會被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})
  • 以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套組件而無須設置嵌套的路徑。
  • 如果想在父路由渲染內容,可以定義一個空的子路由。

編程式的導航

router.push(location, onComplete?, onAbort?)

導航到不同的 URL,會向 history 棧添加一個新的記錄。
在 Vue 實例內部,調用 this.$router.push

// 字符串
router.push('home')
// 對象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 帶查詢參數,變成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
  • onComplete:在導航成功完成 (在所有的異步鉤子被解析之後) 調用
  • onAbort:在導航終止 (導航到相同的路由、或在當前導航完成之前導航到另一個不同的路由) 調用

router.replace(location, onComplete?, onAbort?)

和router.push功能一樣,唯一區別就是不會向 history 添加新記錄

router.go(n)

前進或後退nN步,類似 window.history.go(n)

// 在瀏覽器記錄中前進一步,等同於 history.forward()
router.go(1)
// 後退一步記錄,等同於 history.back()
router.go(-1)

重定向

const router = new VueRouter({
  routes: [
    //path
    { path: '/a', redirect: '/b' },
    //name
    { path: '/a', redirect: { name: 'foo' }},
    //方法
    { path: '/a', redirect: to => {
      // 方法接收 目標路由 作爲參數
      // return 重定向的 字符串路徑/路徑對象
    }}
  ]
})

別名

/a 的別名是 /b,意味着,當用戶訪問 /b 時,URL 會保持爲 /b,但是路由匹配則爲 /a,就像用戶訪問 /a 一樣

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

路由組件傳參

使用 props 將組件和路由解耦

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

    // 對於包含命名視圖的路由,你必須分別爲每個命名視圖添加 `props` 選項:
    {
      path: '/user/:id',
      components: { default: User, sidebar: Sidebar },
      props: { default: true, sidebar: false }
    }
  ]
})
  • 如果 props 被設置爲 true,route.params 將會被設置爲組件屬性
  • 如果 props 是一個對象,它會被按原樣設置爲組件屬性。當 props 是靜態的時候有用。
const router = new VueRouter({
  routes: [
    { path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }
  ]
})
  • 你可以創建一個函數返回 props。這樣你便可以將參數轉換成另一種類型,將靜態值與基於路由的值結合等等。
const router = new VueRouter({
  routes: [
    { path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
  ]
})

HTML5 History 模式

vue-router 默認 hash 模式。

開啓history 模式,將充分利用 history.pushState API 來完成 URL 跳轉而無須重新加載頁面,但需要後端配合。

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

導航守衛

vue-router 提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。

全局

//前置守衛:確保要調用 next 方法,否則鉤子就不會被 resolved。
router.beforeEach((to, from, next) => {
  // ...
})
//後置守衛
router.afterEach((to, from) => {
  // ...
})
//解析守衛:在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之後,解析守衛就被調用
router.beforeResolve((to, from) => {
  // ...
})
參數說明
  • to: Route: 即將要進入的目標 路由對象
  • from: Route: 當前導航正要離開的路由
  • next: Function: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。

    • next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
    • next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了(可能是用戶手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
    • next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向 next 傳遞任意位置對象,且允許設置諸如 replace: true、name: 'home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。
    • next(error): (2.4.0+) 如果傳入 next 的參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError() 註冊過的回調。

路由獨享的守衛

//與全局前置守衛的方法參數是一樣的
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

組件內的守衛

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染該組件的對應路由被 confirm 前調用
    // 不!能!獲取組件實例 `this`
    // 因爲當守衛執行前,組件實例還沒被創建
  },
  beforeRouteUpdate (to, from, next) {
    // 在當前路由改變,但是該組件被複用時調用
    // 舉例來說,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
    // 由於會渲染同樣的 Foo 組件,因此組件實例會被複用。而這個鉤子就會在這個情況下被調用。
    // 可以訪問組件實例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 導航離開該組件的對應路由時調用
    // 可以訪問組件實例 `this`
  }
}

beforeRouteEnter 守衛 不能 訪問 this,因爲守衛在導航確認前被調用,因此即將登場的新組件還沒被創建。可以通過傳一個回調給 next來訪問組件實例。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通過 `vm` 訪問組件實例
  })
}

解析流程

  1. 導航被觸發。
  2. 在失活的組件裏調用離開守衛。
  3. 調用全局的 beforeEach 守衛。
  4. 在重用的組件裏調用 beforeRouteUpdate 守衛 (2.2+)。
  5. 在路由配置裏調用 beforeEnter。
  6. 解析異步路由組件。
  7. 在被激活的組件裏調用 beforeRouteEnter。
  8. 調用全局的 beforeResolve 守衛 (2.5+)。
  9. 導航被確認。
  10. 調用全局的 afterEach 鉤子。
  11. 觸發 DOM 更新。
  12. 用創建好的實例調用 beforeRouteEnter 守衛中傳給 next 的回調函數。

路由元信息

配置meta字段記錄元信息

//定義
{
  path: 'bar',
  component: Bar,
  // a meta field
  meta: { requiresAuth: true }
}
//訪問
$route.matched

過渡動效

<transition>
  <router-view></router-view>
</transition>
<!-- 使用動態的 transition name -->
<transition :name="transitionName">
  <router-view></router-view>
</transition>

14.Vuex

安裝

  • 直接用 <script> 引入(本地或者cdn)
  • npm npm install vuex

必須要通過 Vue.use() 明確地安裝vuex,且要通過 store 配置參數注入Vue實例。

簡介

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

image

state

  • 狀態倉庫,全局的數據共享中心
  • 改變 state 中的狀態的唯一途徑就是顯式地提交 (commit) mutation
  • 獲取狀態使用計算屬性
  • mapState輔助函數
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // 箭頭函數可使代碼更簡練
    count: state => state.count,

    // 傳字符串參數 'count' 等同於 `state => state.count`
    countAlias: 'count',

    // 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}
--------------
//當映射的計算屬性的名稱與 state 的子節點名稱相同時,我們也可以給 mapState 傳一個字符串數組
computed: mapState([
  // 映射 this.count 爲 store.state.count
  'count'
])
--------------
computed: {
  localComputed () { /* ... */ },
  // 使用對象展開運算符將此對象混入到外部對象中
  ...mapState({
    // ...
  })
}

Getters

如果有多處都需要從store中派生(進行二次處理)出一些狀態,那麼可以使用getter(store 的計算屬性,依賴更新)

getters: {
    //state 作爲其第一個參數
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    //接受其他 getter 作爲第二個參數
    doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
//調用
store.getters.doneTodosCount // -> 1
  • mapGetters輔助函數將 store 中的 getter 映射到局部計算屬性,用法和mapState一樣
import { mapGetters } from 'vuex'
computed: {
    // 使用對象展開運算符將 getter 混入 computed 對象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
}

Mutations

更改 Vuex 的 store 中的狀態的唯一方法是提交 (commit)。建議名字大寫。 mutation,但是請勿進行異步操作。

mutations: {
    increment (state) {
      // 變更狀態
      state.count++
    }
}
//調用
store.commit('increment')
  • 提交載荷(Payload)

載荷即commit中額外的參數。

mutations: {
  increment (state, n) {
    state.count += n
  }
}
//調用
store.commit('increment', 10)
-------------
//推薦使用對象
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})
//對象風格提交
store.commit({
  type: 'increment',
  amount: 10
})
  • mapMutations 輔助函數將組件中的 methods 映射爲 store.commit 調用
import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`

      // `mapMutations` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
    })
  }
}

Actions

Action 類似於 mutation,區別在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意異步操作。
actions: {
    increment (context) {
      context.commit('increment')
    }
}
//分發
store.dispatch('increment')

Action 函數接受一個與 store 實例(不是 store 實例本身)具有相同方法和屬性的 context(context.state/context.getters/context.commit)對象,也可以運用參數結構。

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}
  • 同樣支持載荷和對象方式
// 以載荷形式分發
store.dispatch('incrementAsync', {
  amount: 10
})

// 以對象形式分發
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
  • mapActions 輔助函數將組件的 methods 映射爲 store.dispatch 調用
import { mapActions } from 'vuex'

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`

      // `mapActions` 也支持載荷:
      'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
    })
  }
}
  • Promise
actions: {
  actionA ({ commit }) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit('someMutation')
        resolve()
      }, 1000)
    })
  },
  //調用其他action
  actionB ({ dispatch, commit }) {
    return dispatch('actionA').then(() => {
      commit('someOtherMutation')
    })
  }
}
store.dispatch('actionA').then(() => {
  // ...
})
  • async / await
// 假設 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

Modules

將store分割成模塊,每個模塊擁有自己的 state、mutation、action、getter。

  • 獲取根節點狀態rootState
actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
}
getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
}
  • 命名空間:所有 getter、action 及 mutation 都會自動根據模塊註冊的路徑調整命名
 modules: {
    account: {
      namespaced: true,

      // 模塊內容(module assets)
      state: {}, // 模塊內的狀態已經是嵌套的了,使用 `namespaced` 屬性不會對其產生影響
      getters: {},
      actions: {},
      mutations: {}
    }
}
  • 訪問全局state 和 getter
getters: {
  // 在這個模塊的 getter 中,`getters` 被局部化了
  // 你可以使用 getter 的第四個參數來調用 `rootGetters`
  someGetter (state, getters, rootState, rootGetters) {}
}
actions: {
    // 在這個模塊中, dispatch 和 commit 也被局部化了
    // 他們可以接受 `root` 屬性以訪問根 dispatch 或 commit
    someAction ({ dispatch, commit, getters, rootGetters }) {}
}

帶命名空間的綁定函數

  • mapState, mapGetters, mapActions 和 mapMutations的第一個參數接受模塊的空間名稱字符串
computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo',
    'bar'
  ])
}
  • createNamespacedHelpers創建
import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

模塊動態註冊store.registerModule

// 註冊模塊 `myModule`
store.registerModule('myModule', {
  // ...
})
// 註冊嵌套模塊 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

使用store.unregisterModule(moduleName) 來動態卸載模塊

15.自定義指令

在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令

創建Vue的自定義指令的這五個鉤子函數都是可選的,不一定要全部出現。而這其中bind和update兩個鉤子函數是最有用的。在實際使用的時候,我們應該根據需求做不同的選擇。比如在恰當的時間通過bind鉤子函數去初始化實例,update鉤子函數去做對應的參數更新和使用unbind鉤子函數去釋放實例資源佔用等。

bind(el, binding, vnode)
inserted(el, binding, vnode)
update(el, binding, vnode, oldVnode)
componentUpdated(el, binding, vnode, oldVnode)
unbind(el, binding, vnode)

image

鉤子函數參數

指令鉤子函數會被傳入以下參數:

el:指令所綁定的元素,可以用來直接操作DOM
binding:一個對象,這個對象包含一些屬性,稍後列出每個屬性的含義
vnode:Vue編譯生成的虛擬節點。有關於VNode更多的資料,可以閱讀VNode相關的API
oldVnode:上一個虛擬節點,僅在update和componentUpdated兩個鉤子函數中可用

binding參數是一個對象,其包含以下一些屬性:

name:指令名,不包括v-前綴
value:指令的綁定值,如例v-hello = "1 + 1"中,綁定值爲2
oldValue:指令綁定的前一個值,僅在update和componentUpdated鉤子中可用,無論值是否改變都可用
expression:字符串形式的指令表達式。例如v-hello = "1 + 1"中,表達式爲"1 + 1"
arg:傳給指令的參數,可選。例如v-hello:message中,參數爲"message"
modifiers:一個包含修飾符的對象。例如v-hello.foo.bar中,修飾符對象爲{foo:true, bar:true}

除了 el 之外,其它參數都應該是隻讀的,切勿進行修改。如果需要在鉤子之間共享數據,建議通過元素的 dataset 來進行。著作權歸作者所有。

函數簡寫

在很多時候,你可能想在 bind 和 update 時觸發相同行爲,而不關心其它的鉤子。比如這樣寫:

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

對象字面量

如果指令需要多個值,可以傳入一個 JavaScript 對象字面量。記住,指令函數能夠接受所有合法的 JavaScript 表達式。

<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!"
})

16.編碼規範

編碼規範

  1. 每一個 Vue 組件(等同於模塊)首先)必須專注於解決一個單一的問題,獨立的、可複用的、微小的 和 可測試的。
  2. 組件名應該始終是多個單詞的,根組件 App 除外,切勿使用保留字;原則:有意義的,簡短,可讀性。
  3. 組件的data必須是一個帶return的函數
  4. props儘可能使用原始類型的數據,且需要指定其類型
  5. 爲 v-for 設置鍵值,避免v-if 和 v-for 同時用在同一個元素上
  6. 爲組件樣式設置作用域
  7. 組件結構合理,簡潔,添加name
  8. 謹慎使用this.$parent,this.$refs
  9. 單文件組件文件名的大小寫(採用大駝峯),模板中使用連字符kebab-case
  10. 指令縮寫

參考資料:
https://pablohpsilva.github.i...
https://vue-loader.vuejs.org/...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章