vue從入門到進階:指令與事件(二)

風雨後見彩虹發表於2018-01-12

一.插值

v-once

通過使用 v-once 指令,你也能執行一次性地插值,當資料改變時,插值處的內容不會更新。但請留心這會影響到該節點上所有的資料繫結:

span v-once>這個將不會改變: {{ msg }}</span>

v-html

雙大括號會將資料解釋為普通文字,而非 HTML 程式碼。為了輸出真正的 HTML,你需要使用 v-html 指令:

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

這個 span 的內容將會被替換成為屬性值 rawHtml,直接作為 HTML——會忽略解析屬性值中的資料繫結。

你的站點上動態渲染的任意 HTML 可能會非常危險,因為它很容易導致 XSS 攻擊。請只對可信內容使用 HTML 插值,絕不要對使用者提供的內容使用插值。

使用 JavaScript 表示式

Vue.js 都提供了完全的 JavaScript 表示式支援。

{{ number + 1 }}

{{ ok ? `YES` : `NO` }}

{{ message.split(``).reverse().join(``) }}

<div v-bind:id="`list-` + id"></div>

這些表示式會在所屬 Vue 例項的資料作用域下作為 JavaScript 被解析。有個限制就是,每個繫結都只能包含單個表示式,所以下面的例子都不會生效

<!-- 這是語句,不是表示式 -->
{{ var a = 1 }}

<!-- 流控制也不會生效,請使用三元表示式 -->
{{ if (ok) { return message } }}

二.v-bind與v-on的縮寫

v-bind 縮寫

<!-- 完整語法 -->
<a v-bind:href="url"></a>

<!-- 縮寫 -->
<a :href="url"></a>

<!-- 完整語法 -->
<button v-bind:disabled="isButtonDisabled">Button</button>

<!-- 縮寫 -->
<button :disabled="isButtonDisabled">Button</button>

說明下:如果isButtonDisabled的值是 nullundefinedfalse,則 disabled 特性甚至不會被包含在渲染出來的 <button> 元素中。

v-on 縮寫

<!-- 完整語法 -->
<a v-on:click="doSomething">...</a>

<!-- 縮寫 -->
<a @click="doSomething">...</a>

三.條件渲染

v-if

<h1 v-if="ok">Yes</h1>

在 <template> 元素上使用 v-if

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

v-else

你可以使用 v-else 指令來表示 v-if 的“else 塊”:

<div v-if="Math.random() > 0.5">
  Now you see me
</div>
<div v-else>
  Now you don`t
</div>

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

用 key 管理可複用的元素

Vue 會盡可能高效地渲染元素,通常會複用已有元素而不是從頭開始渲染。這麼做除了使 Vue 變得非常快之外,還有其它一些好處。例如,如果你允許使用者在不同的登入方式之間切換:

<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 將不會清除使用者已經輸入的內容。因為兩個模板使用了相同的元素,<input> 不會被替換掉——僅僅是替換了它的 placeholder

這樣也不總是符合實際需求,所以 Vue 為你提供了一種方式來表達“這兩個元素是完全獨立的,不要複用它們”。只需新增一個具有唯一值的 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> 元素仍然會被高效地複用,因為它們沒有新增 key 屬性。

v-show

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

不同的是帶有 v-show 的元素始終會被渲染並保留在 DOM 中。v-show 只是簡單地切換元素的 CSS 屬性 display

注意,v-show 不支援 <template> 元素,也不支援 v-else。

v-if vs v-show

一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在執行時條件很少改變,則使用 v-if 較好。

v-if 與 v-for 一起使用

v-ifv-for 一起使用時,v-for 具有比 v-if 更高的優先順序

四.列表渲染v-for

一個陣列的v-for

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>

var example1 = new Vue({
  el: `#example-1`,
  data: {
    items: [
      { message: `Foo` },
      { message: `Bar` }
    ]
  }
})

在 v-for 塊中,我們擁有對父作用域屬性的完全訪問許可權。v-for 還支援一個可選的第二個引數為當前項的索引。

<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` }
    ]
  }
})

結果:

Parent - 0 - Foo
Parent - 1 - Bar

你也可以用 of 替代 in 作為分隔符,因為它是最接近 JavaScript 迭代器的語法:

<div v-for="item of items"></div>

一個物件的 v-for

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>

new Vue({
  el: `#v-for-object`,
  data: {
    object: {
      firstName: `John`,
      lastName: `Doe`,
      age: 30
    }
  }
})

結果:

John
Doe
30

你也可以提供第二個的引數為鍵名:

<div v-for="(value, key) in object">
  {{ key }}: {{ value }}
</div>

結果:

firstName: John
lastName: Doe
age: 30

第三個引數為索引:

<div v-for="(value, key, index) in object">
  {{ index }}. {{ key }}: {{ value }}
</div>

在遍歷物件時,是按 Object.keys() 的結果遍歷,但是不能保證它的結果在不同的 JavaScript 引擎下是一致的。

key

為了給 Vue 一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key 屬性。理想的 key 值是每項都有的且唯一的 id。

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

建議儘可能在使用 v-for 時提供 key,除非遍歷輸出的 DOM 內容非常簡單,或者是刻意依賴預設行為以獲取效能上的提升。

陣列更新檢測

變異方法與替換陣列

Vue.js 包裝了被觀察陣列的變異方法,故它們能觸發檢視更新。被包裝的方法有:push(), pop(), shift(), unshift(), splice(), sort(), reverse()

你開啟控制檯,然後用前面例子的 items 陣列呼叫變異方法:example1.items.push({ message: `Baz` })

變異方法 (mutation method),顧名思義,會改變被這些方法呼叫的原始陣列。相比之下,也有非變異 (non-mutating method) 方法,例如:filter(), concat()slice() 。這些不會改變原始陣列,但總是返回一個新陣列。當使用非變異方法時,可以用新陣列替換舊陣列:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

注意事項

由於 JavaScript 的限制,Vue 不能檢測以下變動的陣列:

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

為了解決第一類問題,以下兩種方式都可以實現和 vm.items[indexOfItem] = newValue 相同的效果,同時也將觸發狀態更新:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)

為了解決第二類問題,你可以使用 splice:

example1.items.splice(newLength)

物件更改檢測注意事項

還是由於 JavaScript 的限制,Vue 不能檢測物件屬性的新增或刪除:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 現在是響應式的

vm.b = 2
// `vm.b` 不是響應式的

對於已經建立的例項,Vue 不能動態新增根級別的響應式屬性。但是,可以使用 Vue.set(object, key, value) 方法向巢狀物件新增響應式屬性。例如,對於:

ar vm = new Vue({
  data: {
    userProfile: {
      name: `Anika`
    }
  }
})

你可以新增一個新的 age 屬性到巢狀的 userProfile 物件:

Vue.set(vm.userProfile, `age`, 27)

你還可以使用 vm.$set 例項方法,它只是全域性 Vue.set 的別名:

vm.$set(this.userProfile, `age`, 27)

有時你可能需要為已有物件賦予多個新屬性,比如使用 Object.assign() _.extend()。在這種情況下,你應該用兩個物件的屬性建立一個新的物件。所以,如果你想新增新的響應式屬性,不要像這樣:

Object.assign(this.userProfile, {
  age: 27,
  favoriteColor: `Vue Green`
})

你應該這樣做:

this.userProfile = Object.assign({}, this.userProfile, {
  age: 27,
  favoriteColor: `Vue Green`
})

顯示過濾/排序結果

有時,我們想要顯示一個陣列的過濾或排序副本,而不實際改變或重置原始資料。在這種情況下,可以建立返回過濾或排序陣列的計算屬性。

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

在計算屬性不適用的情況下 (例如,在巢狀 v-for 迴圈中) 你可以使用一個 method 方法:

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

值域 v-for

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

template-v-for

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

一個元件的 v-for

<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0+ 的版本里,當在元件中使用 v-for 時,key 現在是必須的。

然而,任何資料都不會被自動傳遞到元件裡,因為元件有自己獨立的作用域。為了把迭代資料傳遞到元件裡,我們要用 props

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

不自動將 item 注入到元件裡的原因是,這會使得元件與 v-for 的運作緊密耦合。明確元件資料的來源能夠使元件在其他場合重複使用。

<div id="todo-list-example">
  <input
    v-model="newTodoText"
    v-on:keyup.enter="addNewTodo"
    placeholder="Add a todo"
  >
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>

注意這裡的 is="todo-item" 屬性。這種做法在使用 DOM 模板時是十分必要的,因為在 <ul> 元素內只有 <li> 元素會被看作有效內容。這樣做實現的效果與 <todo-item> 相同,但是可以避開一些潛在的瀏覽器解析錯誤。

Vue.component(`todo-item`, {
  template: `
    <li>
      {{ title }}
      <button v-on:click="$emit(`remove`)">X</button>
    </li>
  `,
  props: [`title`]
})

new Vue({
  el: `#todo-list-example`,
  data: {
    newTodoText: ``,
    todos: [
      {
        id: 1,
        title: `Do the dishes`,
      },
      {
        id: 2,
        title: `Take out the trash`,
      },
      {
        id: 3,
        title: `Mow the lawn`
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function () {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ``
    }
  }
})

v-for 與 v-if

當它們處於同一節點,v-for 的優先順序比 v-if 更高,這意味著 v-if 將分別重複執行於每個 v-for 迴圈中。當你想為僅有的一些項渲染節點時,這種優先順序的機制會十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

上面的程式碼只傳遞了未完成的 todos。

而如果你的目的是有條件地跳過迴圈的執行,那麼可以將 v-if 置於外層元素 (或 <template>)上。如:

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

五.事件處理

事件處理方法

<div id="example-2">
  <!-- `greet` 是在下面定義的方法名 -->
  <button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
  el: `#example-2`,
  data: {
    name: `Vue.js`
  },
  // 在 `methods` 物件中定義方法
  methods: {
    greet: function (event) {
      // `this` 在方法裡指向當前 Vue 例項
      alert(`Hello ` + this.name + `!`)
      // `event` 是原生 DOM 事件
      if (event) {
        alert(event.target.tagName)
      }
    }
  }
})

// 也可以用 JavaScript 直接呼叫方法
example2.greet() // => `Hello Vue.js!`

內聯處理器中的方法

<div id="example-3">
  <button v-on:click="say(`hi`)">Say hi</button>
  <button v-on:click="say(`what`)">Say what</button>
</div>

new Vue({
  el: `#example-3`,
  methods: {
    say: function (message) {
      alert(message)
    }
  }
})

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

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

// ...
methods: {
  warn: function (message, event) {
    // 現在我們可以訪問原生事件物件
    if (event) event.preventDefault()
    alert(message)
  }
}

事件修飾符

修飾符是由點開頭的指令字尾來表示的。

  • .stop
  • .prevent
  • .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>

使用修飾符時,順序很重要;相應的程式碼會以同樣的順序產生。因此,用 @click.prevent.self 會阻止所有的點選,而 @click.self.prevent 只會阻止對元素自身的點選。

<!-- 點選事件將只會觸發一次 2.1.4 新增-->
<a v-on:click.once="doThis"></a>

不像其它只能對原生的 DOM 事件起作用的修飾符,.once 修飾符還能被用到自定義的元件事件上。

<!-- the scroll event will not cancel the default scroll behavior 2.3.0 新增 -->
<div v-on:scroll.passive="onScroll">...</div>

Vue 為這些修飾符額外提供了 .passive 修飾符來提升移動端的效能。舉個例子,在滾動的時候,瀏覽器會在整個事件處理完畢之後再觸發滾動,因為瀏覽器並不知道這個事件是否在其處理函式中被呼叫了 event.preventDefault().passive 修飾符用來進一步告訴瀏覽器這個事件的預設行為不會被取消。

不要把 .passive 和 .prevent 一起使用。被動處理函式無法阻止預設的事件行為。

按鍵修飾符

<!-- 只有在 keyCode 是 13 時呼叫 vm.submit() -->
<input v-on:keyup.13="submit">
<!-- 同上 -->
<input v-on:keyup.enter="submit">
<!-- 縮寫語法 -->
<input @keyup.enter="submit">

全部的按鍵別名:

  • .enter
  • .tab
  • .delete (捕獲“刪除”和“退格”鍵)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

可以通過全域性 config.keyCodes 物件自定義按鍵修飾符別名:

// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112

自動匹配按鍵修飾符(2.5.0 新增)

你也可直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉換為 kebab-case 來作為修飾符:

<input @keyup.page-down="onPageDown">

在上面的例子中,處理函式僅在 $event.key === `PageDown` 時被呼叫。

有一些按鍵 (.esc 以及所有的方向鍵) 在 IE9 中有不同的 key 值, 如果你想支援 IE9,它們的內建別名應該是首選。

系統修飾鍵(2.1.0 新增)

可以用如下修飾符來實現僅在按下相應按鍵時才觸發滑鼠或鍵盤事件的監聽器。

  • .ctrl
  • .alt
  • .shift
  • .meta

注意:在 Mac 系統鍵盤上,meta 對應 command 鍵 (⌘)。在 Windows 系統鍵盤 meta 對應 Windows 徽標鍵 (⊞)。在 Sun 作業系統鍵盤上,meta 對應實心寶石鍵 (◆)。在其他特定鍵盤上,尤其在 MIT 和 Lisp 機器的鍵盤、以及其後繼產品,比如 Knight 鍵盤、space-cadet 鍵盤,meta 被標記為“META”。在 Symbolics 鍵盤上,meta 被標記為“META”或者“Meta”。

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

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

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

.exact 修飾符(2.5.0 新增)

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

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

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

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

滑鼠按鈕修飾符(2.1.0 新增)

  • .left
  • .right
  • .middle

這些修飾符會限制處理函式僅響應特定的滑鼠按鈕。

六.表單輸入繫結v-model

你可以用 v-model 指令在表單 <input> <textarea> 元素上建立雙向資料繫結。

v-model 會忽略所有表單元素的 valuecheckedselected 特性的初始值而總是將 Vue 例項的資料作為資料來源。你應該通過 JavaScript 在元件的 data 選項中宣告初始值。

對於需要使用輸入法 (如中文、日文、韓文等) 的語言,你會發現 v-model 不會在輸入法組合文字過程中得到更新。如果你也想處理這個過程,請使用 input 事件。

文字

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>

多行文字

<span>Multiline message is:</span>
<p style="white-space: pre-line;">{{ message }}</p>
<br>
<textarea v-model="message" placeholder="add multiple lines"></textarea>

在文字區域插值 (<textarea></textarea>) 並不會生效,應用 v-model 來代替。

核取方塊

單個核取方塊,繫結到布林值:

<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>

多個核取方塊,繫結到同一個陣列:

<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: []
  }
})

看下面一個例子:

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no"
>
// 當選中時
vm.toggle === `yes`
// 當沒有選中時
vm.toggle === `no`

這裡的 true-value false-value 特性並不會影響輸入控制元件的 value 特性,因為瀏覽器在提交表單時並不會包含未被選中的核取方塊。如果要確保表單中這兩個值中的一個能夠被提交,(比如“yes”或“no”),請換用單選按鈕。

單選按鈕

<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: ``
  }
})

選擇框

單選時:

<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: ``
  }
})

如果 v-model 表示式的初始值未能匹配任何選項,<select> 元素將被渲染為“未選中”狀態。在 iOS 中,這會使使用者無法選擇第一個選項。因為這樣的情況下,iOS 不會觸發 change 事件。因此,更推薦像上面這樣提供一個值為空的禁用選項。

多選時 (繫結到一個陣列):

<div id="example-6">
  <select v-model="selected" multiple style="width: 50px;">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <br>
  <span>Selected: {{ selected }}</span>
</div>

new Vue({
  el: `#example-6`,
  data: {
    selected: []
  }
})

v-for 渲染的動態選項:

<select v-model="selected">
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  </option>
</select>
<span>Selected: {{ selected }}</span>

new Vue({
  el: `...`,
  data: {
    selected: `A`,
    options: [
      { text: `One`, value: `A` },
      { text: `Two`, value: `B` },
      { text: `Three`, value: `C` }
    ]
  }
})

修飾符.lazy,.number,.trim

.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 輸入元素的值也總會返回字串。

.trim

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

相關文章