Vue.js 2.0 快速上手 - 基礎篇

Vue 2.0 出來也有一段時間了,作為一個有志向的全面發展好青年,在征服 Vue 1.x,React,React Native 後,為了之後能更快遷移公司的項目到 Vue 2.x,於是決定先看看 Vue 2.0。

鑒於部分讀者可能不了解 Vue,先簡單看看各種特性。

本文假設你有一定的 HTML 基礎,並熟悉一種或以上編程語言(那就能看懂 JS 了)。

模板語法

Vue 提供了一堆數據綁定語法。

  • {{ text }} 文本插值
  • <div v-html="html"></div> HTML 輸出
  • v-bind HTML 屬性插值。如<button v-bind:disabled="someDynamicCondition">Button</button>
  • JavaScript 表達式。直接在 mustache、屬性插值裡面使用各種表達式(加減乘除、三元運算、方法調用等)。
  • 過濾器(有點類似 Shell 命令中的管道,可以定義過濾器來對原始值進行變化)。
  • 指令。之前提到的 v-bind 也是一種指定,其他包括 v-on: 系列(dom 事件的監聽)、v-for、v-model等。

Vue 實例

Vue 實例,實則也就是 ViewModel(數據 + 函數),都是通過構造函數 Vue 創建的:

var data = { a: 1 }nvar vm = new Vue({n el: #example,n data: data,n created: function () {n // `this` 指向 vm 實例n console.log(a is: + this.a)n }n})nvm.$data === data // -> truenvm.$el === document.getElementById(example) // -> truen// $watch 是一個實例方法nvm.$watch(a, function (newVal, oldVal) {n // 這個回調會在 `vm.a` 改變的時候觸發n})n

Vue 實例都有自己的生命周期,比如 created, mounted, updated 以及 destroyed。所有方法被 called 的時候,this 都指向所在的 Vue 實例。

Lifecycle 圖如下:

計算屬性和監聽器

計算屬性

其實就是一個需要計算的 getter:

<div id="example">n <p>Original message: "{{ message }}"</p>n <p>Computed reversed message: "{{ reversedMessage }}"</p>n</div>n

var vm = new Vue({n el: #example,n data: {n message: Hellon },n computed: {n // 一個 computed gettern reversedMessage: function () {n // `this` 指向 vm 實例n return this.message.split().reverse().join()n }n }n})n</div>n

和使用 method 的區別在於,計算屬性根據它的依賴被緩存,即如果 message 沒有被修改,下次 get 不會進行重複計算,而 method 則每次調用都會重新計算。這也意味著如 Date.now() 這樣返回的計算屬性會永遠得不到更新。

Setter

默認情況下,計算屬性只有一個 getter,我們也可以給它加上 setter:

computed: {n fullName: {n // gettern get: function () {n return this.firstName + + this.lastNamen },n // settern set: function (newValue) {n var names = newValue.split( )n this.firstName = names[0]n this.lastName = names[names.length - 1]n }n }n}n

如此,當我們調用 vm.fullName = MarkZhai 的時候,firstName 和 lastName 都會被更新。

監聽器

Vue 的 watch 也可以用來做類似的事:

<div id="demo">{{ fullName }}</div>n

var vm = new Vue({n el: #demo,n data: {n firstName: Foo,n lastName: Bar,n fullName: Foo Barn },n watch: {n firstName: function (val) {n this.fullName = val + + this.lastNamen },n lastName: function (val) {n this.fullName = this.firstName + + valn }n }n})n

對比一下計算屬性版本:

var vm = new Vue({n el: #demo,n data: {n firstName: Foo,n lastName: Barn },n computed: {n fullName: function () {n return this.firstName + + this.lastNamen }n }n})n

看上去好像簡單了很多,那還要 Watcher 幹啥呢。。。主要應用場景是非同步或耗時操作:

<script>nvar watchExampleVM = new Vue({n el: #watch-example,n data: {n question: ,n answer: I cannot give you an answer until you ask a question!n },n watch: {n // 只要 question 改變,這個函數就會執行n question: function (newQuestion) {n this.answer = Waiting for you to stop typing...n this.getAnswer()n }n },n methods: {n // _.debounce is a function provided by lodash to limit hown // often a particularly expensive operation can be run.n // In this case, we want to limit how often we accessn // yesno.wtf/api, waiting until the user has completelyn // finished typing before making the ajax request. To learnn // more about the _.debounce function (and its cousinn // _.throttle), visit: Lodash Documentationn getAnswer: _.debounce(n function () {n var vm = thisn if (this.question.indexOf(?) === -1) {n vm.answer = Questions usually contain a question mark. ;-)n returnn }n vm.answer = Thinking...n axios.get(https://yesno.wtf/api)n .then(function (response) {n vm.answer = _.capitalize(response.data.answer)n })n .catch(function (error) {n vm.answer = Error! Could not reach the API. + errorn })n },n // 等待用戶停止輸入後的時間(毫秒)n 500n )n }n})n</script>n

如此,使用 watch 讓我們可以進行非同步操作(訪問 API),限制操作間隔,並設置中間狀態直到獲得了真正的答案。

除了使用 watch option,也可以用 vm.$watch API。

Class 和 Style 綁定

除了數據綁定,常見的還有 style、class 的綁定(正如很久以前在 JQuery 中常用的)。

對象語法

我們可以傳遞一個對象給 v-bind:class 來動態切換 classes:

<div class="static"n v-bind:class="{ active: isActive, text-danger: hasError }">n</div>n

對應的 active 和 text-danger 則通過 data 傳遞過來。

我們也可直接通過 data 把 class 傳遞過來

<div v-bind:class="classObject"></div>n

data: {n classObject: {n active: true,n text-danger: falsen }n}n

當然我們也能使用上面提到的 computed 來進行對應屬性,如 active 的計算。

數組語法

可以直接傳遞一個數組給 v-bind:class:

<div v-bind:class="[activeClass, errorClass]">n

data: {n activeClass: active,n errorClass: text-dangern}n

也可以寫成

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

綁定內聯樣式

跟 class 差不多:

<div v-bind:stylex="{ color: activeColor, fontSize: fontSize + px }"></div>n

或者直接綁定到 style:

<div v-bind:stylex="styleObject"></div>nndata: {n styleObject: {n color: red,n fontSize: 13pxn }n}n

類似的,也有數組綁定。

條件綁定

v-if

其實就是個標籤啦

<h1 v-if="ok">Yes</h1>nn<h1 v-if="ok">Yes</h1>n<h1 v-else>No</h1>n

因為 v-if 必須附加到一個單一 element 上,那如果我們想切換多個元素呢?可以使用 template 元素:

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

v-show

也可以用 v-show 來做條件顯示的邏輯,

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

區別在於

  • v-show 不支持 template 和 v-else
  • v-if 是 lazy 的,不會渲染沒有走到的條件。而 v-show 只是簡單的基於 CSS 的切換。所以 v-show 的初始 render 代價較高。
  • 由於 v-if 是真實的渲染,切換後原來的 dom 會被 destroyed,而新的 dom 會被重新創建。所以切換代價更高。

所以如果切換得較為頻繁可以使用 v-show,如果在運行時不太會改變則可以使用 v-if。

列表渲染

v-for

其實就是個循環標籤啦:

<ul id="example-2">n <li v-for="(item, index) in items">n {{ parentMessage }} - {{ index }} - {{ item.message }}n </li>n</ul>n

對應的 vm 實例:

var example2 = new Vue({n el: #example-2,n data: {n parentMessage: Parent,n items: [n { message: Foo },n { message: Bar }n ]n }n})n

模板 v-for

跟 v-if 類似,我們也能在 template 上使用 v-for:

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

對象 v-for

也能使用 v-for 遍歷對象的屬性:

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

new Vue({n el: #repeat-object,n data: {n object: {n FirstName: John,n LastName: Doe,n Age: 30n }n }n})n

看到 value,那肯定還有 key 了:

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

如果再加上 index:

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

其他還有像是 v-for="n in 10" 這種用法,就不加上例子了。

組件 v-for

input 輸出內容到 newTodoText,每次點擊 enter 都會觸發 addNewTodo,然後添加 item 到 todos,觸發新的 li 添加進去:

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

Vue.component(todo-item, {n template: n <li>n {{ title }}n <button v-on:click="$emit(remove)">X</button>n </li>n ,n props: [title]n})nnew Vue({n el: #todo-list-example,n data: {n newTodoText: ,n todos: [n Do the dishes,n Take out the trash,n Mow the lawnn ]n },n methods: {n addNewTodo: function () {n this.todos.push(this.newTodoText)n this.newTodoText = n }n }n})n

key

當 vue 在更新被 v-for 渲染的列表時候,會使用就地 patch 的策略,而不是根據元素改變的順序。我們可以提供 key 來做這個排序:

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

如此,item 會根據 id 來做排序。

數組改變監測

替換方法(mutation)

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

這些方法會改變原來的 array,並自動觸發 view 的更新。

替換 array

  • filter()
  • concat()
  • slice()

這幾個方法會返回新的 array,如:

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

附加說明

如果

  • 直接 set array 的值,如 vm.items[indexOfItem] = newValue
  • 修改 array 的長度,如 vm.items.length = newLength

都是沒法觸發更新的,需要使用

Vue.set(example1.items, indexOfItem, newValue)nn// Array.prototype.splice`nexample1.items.splice(indexOfItem, 1, newValue)nnexample1.items.splice(newLength)n

過濾/排序

配合 computed 以及 filter,或者也可以使用 v-for 的條件渲染:

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

事件處理

監聽事件

使用 v-on 指令監聽 DOM 的各種事件,如:

<div id="example-1">n <button v-on:click="counter += 1">Add 1</button>n <p>The button above has been clicked {{ counter }} times.</p>n</div>n

var example1 = new Vue({n el: #example-1,n data: {n counter: 0n }n})n

除了直接寫 JS 語句,也可以直接在 v-on 中調用 methods 中定義的事件,還可以進行傳參:

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

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

我們可能也希望直接把 event 給傳遞到方法中(比如在方法里 preventDefault 或者 stopPropagation),也很 easy,直接使用特殊的 $event 變數就行了。

事件修飾符

除了像上面這樣,在 method 裡面對 event 進行操作,我們還可以使用事件修飾符(Event Modifier):

  • .stop
  • .prevent
  • .capture
  • .self

使用如:

<!-- the click events propagation will be stopped -->n<a v-on:click.stop="doThis"></a>n<!-- the submit event will no longer reload the page -->n<form v-on:submit.prevent="onSubmit"></form>n<!-- modifiers can be chained -->n<a v-on:click.stop.prevent="doThat"></a>n<!-- just the modifier -->n<form v-on:submit.prevent></form>n<!-- use capture mode when adding the event listener -->n<div v-on:click.capture="doThis">...</div>n<!-- only trigger handler if event.target is the element itself -->n<!-- i.e. not from a child element -->n<div v-on:click.self="doThat">...</div>n

Key 修飾符

通用的有使用 keyCode 的:

<input v-on:keyup.13="submit">n

其他 alias 別名有

  • enter
  • tab
  • delete (captures both 「Delete」 and 「Backspace」 keys)
  • esc
  • space
  • up
  • down
  • left
  • right

我們也可以自己通過全局的 config 定義其他別名,如:

// enable v-on:keyup.f1nVue.config.keyCodes.f1 = 112n

表單輸入綁定

基本使用

text

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

如此,用戶的輸入會直接反映到 data 中的 message,然後更新到

多行的用 textarea 替換 input 就行了。

Checkbox

單個的:

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

多個的則可以綁到一個 array :

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">n<label for="jack">Jack</label>n<input type="checkbox" id="john" value="John" v-model="checkedNames">n<label for="john">John</label>n<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">n<label for="mike">Mike</label>n<br>n<span>Checked names: {{ checkedNames }}</span>n

Radio

<input type="radio" id="one" value="One" v-model="picked">n<label for="one">One</label>n<br>n<input type="radio" id="two" value="Two" v-model="picked">n<label for="two">Two</label>n<br>n<span>Picked: {{ picked }}</span>n

Select

<select v-model="selected">n <option>A</option>n <option>B</option>n <option>C</option>n</select>n<span>Selected: {{ selected }}</span>n Selected: Cn

多選的在 select 後面加個 multiple,然後對應的會綁定到數組。

還可以結合 v-for 進行動態渲染:

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

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

值綁定

默認地,像上面這樣,最後 v-model 綁定到的對象,其值會是一個 靜態字元串(或者 true/false),有時候我們想要將其值綁定到一個動態屬性,就可以使用 v-bind 來達到目的。

比如對於 input:

<inputn type="checkbox"n v-model="toggle"n v-bind:true-value="a"n v-bind:false-value="b">n

// when checked:nvm.toggle === vm.an// when unchecked:nvm.toggle === vm.bn

甚至對象:

<select v-model="selected">n <!-- inline object literal -->n <option v-bind:value="{ number: 123 }">123</option>n</select>n

// when selected:ntypeof vm.selected // -> objectnvm.selected.number // -> 123n

修飾符

.lazy

默認地,v-model 在每次 input 事件後都會同步輸入到數據。加上 lazy 修飾符後就會在 change 事件後才同步:

<input v-model.lazy="msg" >n

.number

會自動把輸入轉為 number:

<input v-model.number="age" type="number">n

這還是挺有用的,因為就算限制了 input 的 type 為 number,元素的 value 仍然會返回 string。

.trim

好像不用多說了?大家都懂吧。

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

組件

現代的前端框架,通常都是組件化的了。整個應用的搭建,其實就是組件的拼接。自然 Vue 也不會忘了這個。

使用組件

註冊

註冊一個全局組件,只需要 Vue.component(tagName, options) 即可,如:

Vue.component(my-component, {n // optionsn})n

實際渲染出來的 dom 則定義在 template option 中,如:

// 註冊nVue.component(my-component, {n template: <div>A custom component!</div>n})n// 創建一個根實例nnew Vue({n el: #examplen})n

局部註冊

局部註冊只需要放在 Vue 實例中:

var Child = {n template: <div>A custom component!</div>n}nnew Vue({n // ...n components: {n // <my-component> 只在父親的模板里可用n my-component: Childn }n})n

使用則像:

<div id="example">n <my-component></my-component>n</div>n

Dom 模板解析限制

當使用 Dom 作為模板(比如使用 el 選項來使用已有內容載入元素),將會受到一些因為 HTML 工作原理而導致的限制,因為 Vue 只能在瀏覽器解析後才獲取模板數據並進行處理。比如

中將不能出現自定義組件,只能通過 is 特殊屬性進行規避。

可以通過以下方法使用字元串模板,就不會有這些限制:

  • <script type="text/x-template">
  • JavaScript 內聯模板字元串
  • .vue 組件

所以,盡量使用字元串模板(string templates)吧。

data 必須是函數

大部分被傳進 Vue 構造函數的選項都能在組件內使用,除了一個特殊情況:data 必須是函數。

data: function () {n return {n counter: 0n }n}n

而不能是一個在 parent context 的 var(會被多個組件實例共享)或者 object(控制台會報錯)。

組合組件

組件通常會被一起使用,大部分情況下會有 父——子 關係,如 組件A 在其模板中使用了 組件B。如此,就不免會有相互間的通訊,父親需要傳遞數據給兒子,而兒子則需要通知父親其內部發生的某些事件。

然而,為了讓組件能避免耦合從而提高復用性和可維護性,又需要使它們相對隔離。

在 Vue.js 中,這種 父——子 組件關係可以被總結為 props down, events up,即父組件通過 props 傳遞數據給子組件,而子組件通過 event 發消息給父組件。

熟悉 React 的話,你可能會想到 props 和 state。

props

通過 props 傳遞數據

每個組件都是相互隔離的,所以無法在子組件的 template 中引用父組件的數據。數據只能通過 props 傳遞。

比如我們可以這麼註冊子組件:

Vue.component(child, {n // 申明 propsn props: [message],n // 跟 data 一樣,可以在 vm (this.message) 和 template 中直接使用n template: <span>{{ message }}</span>n})n

然後如此傳遞 props:

<child message="hello!"></child>n

camelCase vs. kebab-case

因為 HTML 屬性的限制(大小寫敏感),所以使用 non-string templates 時,camelCased 的屬性必須使用對應的 kebab-case 版本:

Vue.component(child, {n // camelCase in JavaScriptn props: [myMessage],n template: <span>{{ myMessage }}</span>n})n

<child my-message="hello!"></child>nAgain, if you』re using string templates, then this limitation does not apply.n

所以都說了,用字元串模板吧。

動態 props

<div>n <input v-model="parentMsg">n <br>n <child v-bind:my-message="parentMsg"></child>n</div>n

如此,my-message 在父組件被改變的時候,都會傳遞更新到子組件。

字面量語法 vs 動態語法

當我們使用

<comp some-prop="1"></comp>n

的時候,實際傳遞的是一個字元串,而不是 number 2,如果要傳遞 JavaScript number,則需要使用 v-bind

<comp v-bind:some-prop="1"></comp>n

單向數據流

所有的 props 都是單嚮往下的,父組件 property 更新會影響子組件的,反過來則不會。這樣避免了子組件誤更改父組件狀態,以及應用數據流難以理解。

另外,每次父組件中對應屬性發生改變,子組件中的所有 props 都會被更新為最新的值。所以在子組件中,不應該對 props 進行更改。

你可能會辯解說傳進來的只是個初始值,或者是個需要計算才能得出真正要的格式的值,但對前者你應該使用本地 data 屬性來引用初始值,後者則應該通過 computed 來做。

prop 檢查

可以對組件接受的 props 定義要求,如:

Vue.component(example, {n props: {n // 基本類型檢查 (`null` 表示接受任何類型)n propA: Number,n // 多種可能的類型n propB: [String, Number],n // 一個必須的 stringn propC: {n type: String,n required: truen },n // 一個帶默認值的 numbern propD: {n type: Number,n default: 100n },n // 對象/數組的默認值須通過一個工廠方法返回n propE: {n type: Object,n default: function () {n return { message: hello }n }n },n // 自定義檢驗器函數n propF: {n validator: function (value) {n return value > 10n }n }n }n})n

自定義事件

我們已經學習了父組件如何傳遞屬性給子組件,那子組件怎麼向上發送數據呢?答案就是自定義事件。

使用 v-on

所有 Vue 實例都實現了 Events 介面,即:

  • 通過 $on(eventName) 監聽事件
  • 通過 $emit(eventName) 觸發事件

如此,我們可以這樣來傳遞事件(定義了 2 個 button,可以發送 increment 事件給父組件觸發 incrementTotal)。

<div id="counter-event-example">n <p>{{ total }}</p>n <button-counter v-on:increment="incrementTotal"></button-counter>n <button-counter v-on:increment="incrementTotal"></button-counter>n</div>n

Vue.component(button-counter, {n template: <button v-on:click="increment">{{ counter }}</button>,n data: function () {n return {n counter: 0n }n },n methods: {n increment: function () {n this.counter += 1n this.$emit(increment)n }n },n})nnew Vue({n el: #counter-event-example,n data: {n total: 0n },n methods: {n incrementTotal: function () {n this.total += 1n }n }n})n

表單輸入

有時候在組件中,我們會有輸入,比如 input,其實 input 中的 v-model 就是:

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

的一個語法糖。

類似地,為了讓組件支持 v-model,它必須:

  • 接受 value prop
  • 用新的值發送一個 input 事件

讓我們來看看實踐

<div id="v-model-example">n <p>{{ message }}</p>n <my-inputn label="Message"n v-model="message"n ></my-input>n</div>n

Vue.component(my-input, {n template: n <div class="form-group">n <label v-bind:for="randomId">{{ label }}:</label>n <input v-bind:id="randomId" v-bind:value="value" v-on:input="onInput">n </div>n ,n props: [value, label],n data: function () {n return {n randomId: input- + Math.random()n }n },n methods: {n onInput: function (event) {n this.$emit(input, event.target.value)n }n },n})nnew Vue({n el: #v-model-example,n data: {n message: hellon }n})n

這個介面不僅能被用在組件內的表單輸入,還能被用在你自己發明的各種組件輸入,像是:

<voice-recognizer v-model="question"></voice-recognizer>n<webcam-gesture-reader v-model="gesture"></webcam-gesture-reader>n<webcam-retinal-scanner v-model="retinalImage"></webcam-retinal-scanner>n

非父子組件通訊

有時候兩個組件可能需要互相通訊,但卻不是父子關係。在簡單的場景下,你可以使用一個空的 Vue 實例來作為中央事件匯流排(event bus):

var bus = new Vue()n

// 在組件 A 的方法中nbus.$emit(id-selected, 1)nn// 在組件 B 的 created 中nbus.$on(id-selected, function (id) {n // ...n})n

在更複雜的場景下,你可能需要考慮使用狀態管理模式,其實就是 Vuex 了(Vue 版 Redux)。

使用 Slot 分發內容

在使用組件的時候,經常會像這樣組合:

<app>n <app-header></app-header>n <app-footer></app-footer>n</app>n

有兩點需要注意的:

  • 組件不知道在其掛載點內可能出現的的內容。這是由使用 父組件所決定的。
  • 組件很可能有它自己的模板。

為了讓組件可以組合,我們需要一種方式來混合父組件的內容與子組件自己的模板。這個處理稱為內容分發。Vue.js 實現了一個內容分發 API,參照了當前 Web 組件規範草稿 中 Slot 的 proposal,使用特殊的 元素作為原始內容的插槽。

單個Slot

直接看例子吧:

<div>n <h2>Im the child title</h2>n <slot>n This will only be displayed if there is no contentn to be distributed.n </slot>n</div>n

父組件這樣使用它:

<div>n <h1>Im the parent title</h1>n <my-component>n <p>This is some original content</p>n <p>This is some more original content</p>n </my-component>n</div>n

最後渲染出來的結果是:

<div>n <h1>Im the parent title</h1>n <div>n <h2>Im the child title</h2>n <p>This is some original content</p>n <p>This is some more original content</p>n </div>n</div>n

也就是外面的內容被插入到了slot裡面。

具名 Slot

如果你需要多個 slot,也很簡單:

<div class="container">n <header>n <slot name="header"></slot>n </header>n <main>n <slot></slot>n </main>n <footer>n <slot name="footer"></slot>n </footer>n</div>n

父組件這麼使用

<app-layout>n <h1 slot="header">Here might be a page title</h1>n <p>A paragraph for the main content.</p>n <p>And another one.</p>n <p slot="footer">Heres some contact info</p>n</app-layout>n

渲染出來的結果是:

<div class="container">n <header>n <h1>Here might be a page title</h1>n </header>n <main>n <p>A paragraph for the main content.</p>n <p>And another one.</p>n </main>n <footer>n <p>Heres some contact info</p>n </footer>n</div>n

在設計需要組合到一起的組件時,內容分發 API 是非常有用的機制。

動態組件

你可以使用同一個掛載點,並動態地將其切換為其他 Component。只需要使用保留的 元素並動態綁定到它的 it 屬性:

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

<component v-bind:is="currentView">n <!-- 當 vm.currentView 改變後 component 類型也會發生改變 -->n</component>n

如果你希望的話,也可以直接綁定到 component 對象:

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

keep-alive

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

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

更多詳情可以看 的 API 文檔。

其他雜項

編寫可復用組件

在寫組件的時候,最好想好你是否會在某些其他地方再次用到它。對一些一次性的組件,緊密耦合是沒有問題的,但對可復用的組件,就需要定義一個乾淨的 public 介面,讓它上下文無關。

一個 Vue 組件的 API 由 3 者組成 —— props, events, 以及 slots:

  • Props 允許外部環境傳遞數據到組件內。
  • Events 允許組件觸發外部環境的副效應(side effects)。
  • Slots 允許外部環境來插入內容到組件的視圖結構內。

通過 v-bind 和 v-on 的簡寫語法,template 可以乾淨簡潔地傳遞意圖:

<my-componentn :foo="baz"n :bar="qux"n @event-a="doThis"n @event-b="doThat"n>n <img slot="icon" src="...">n <p slot="main-text">Hello!</p>n</my-component>n

:foo 是 v-bind:foo 的簡寫,@event-a 則是 v-on:event-a 的簡寫。

子組件引用

儘管我們有 props 和 events,有時候你可能仍然需要在 JavaScript 中直接操作子組件。為此你必須使用 ref 分配一個 reference ID 給子組件。

<div id="parent">n <user-profile ref="profile"></user-profile>n</div>n

var parent = new Vue({ el: #parent })n// 訪問子組件實例nvar child = parent.$refs.profilen

當 ref 和 v-for 一起使用的時候,你得到的 ref 將會是一個包含了從數據源鏡像的數組或者對象。

$refs 只有在組件被渲染後才能獲得,而且它不是響應式的。也就意味著只是一個直接子組件操作的逃生口 —— 你應該避免在模板或者 computed 屬性中使用 $refs。

非同步組件

在大型應用中,我們需要把 app 分成一個個小塊,只在真正需要的時候才去載入組件。為了簡化這個,Vue 允許把組件定義為一個工廠方法,並非同步去解析組件定義。Vue 僅僅會在組件真正需要被渲染的時候才會去觸發該工廠方法,然後把結果緩存下來給以後的再渲染。如:

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

工廠方法接受一個 resolve 回調,會在從伺服器獲取到組件定義後被觸發。也可以使用 reject(reason) 來指出載入失敗了。這裡的 setTimeout 只是用來做簡單的演示,如何去獲取 component 完全取決於你。一個推薦的方法是和 Webpack 的 code-splitting 功能一塊兒使用非同步組件:

Vue.component(async-webpack-example, function (resolve) {n // 這個特殊的 require 語法會讓 Webpack 去自動把你的編譯後代碼分割成 通過 Ajax 請求載入的 bundlesn require([./my-async-component], resolve)n})n

也可以在 resolve 方法中返回一個 Promise,比如通過 Webpack 2 + ES2015 語法可以這麼做:

Vue.component(n async-webpack-example,n () => System.import(./my-async-component)n)n

然後 Browserify 不支持非同步組件,擁抱 Webpack 吧。

組件命名規範

在註冊的時候,使用是隨意的:

components: {n kebab-cased-component: { /* ... */ },n camelCasedComponent: { /* ... */ },n TitleCasedComponent: { /* ... */ }n}n

但是在 HTML 模板中,必須使用 kebab-case 的,也就是上面的第一種。但如果是字元串模板(string template)的話,則可以隨意使用。如果你的組件不使用 slot 進行屬性傳遞,甚至可以直接寫成自閉的(也僅支持字元串模板,因為瀏覽器不支持自閉合的自定義元素)。

遞歸組件

組件可以在它自己的模板中遞歸自身。然而,他們只能通過 name 選項來這麼做:

name: stack-overflow,ntemplate: <div><stack-overflow></stack-overflow></div>n

像上面這樣的組件會陷入 「max stack size exceeded」 錯誤,所以需要讓遞歸變成條件性的(比如使用 v-if 指令,並最終返回一個 false)。當你在全局通過 Vue.component 註冊一個組件的時候,一個全局的 ID 會被自動設置為組件的 name 選項。

內聯模板

當子組件中存在 inline-template 這個特殊屬性的時候,它會使用其內容作為模板,而不會把它當做分發內容。如此模板就變得更靈活了。

<my-component inline-template>n <p>These are compiled as the components own template.</p>n <p>Not parents transclusion content.</p>n</my-component>n

但是 inline-template 讓模板的作用域難以理解,並且不能緩存模板編譯結果。最佳實踐是通過 template option 在組件內部定義模板,或者在 .vue 文件中的模板元素中定義。

X-Templates

另一個在 script 元素內部定義模板的方法是通過 type text/x-template,然後通過 id 引用模板。像這樣:

<script type="text/x-template" id="hello-world-template">n <p>Hello hello hello</p>n</script>n

Vue.component(hello-world, {n template: #hello-world-templaten})n

在極小的應用或者大型模板的 demo 的時候可能會有用,其他情況下應該盡量避免。因為這樣會把它和其他模板定義給隔離開。

v-once 定義簡單的靜態組件

在 Vue 裡面渲染純凈的 HTML 元素是很快的,但有時候你可能需要一個包含了很多靜態內容的組件。這種情況下,你可以通過在根元素加上 v-once 指令確保它只被評估了一次然後就被緩存下來了,像是這樣:

Vue.component(terms-of-service, {n template: n <div v-once>n <h1>Terms of Service</h1>n ... 很多靜態內容 ...n </div>n n})n

Vue 1.x TO 2.0

通過使用 vue-migration-helper 可以快速掃出需要替換的代碼。

主要有以下幾類:

  • 官方依賴,比如 vue-resource、vue-router(這個升級完介面也改了不少,遷移嚮導)、vue-loader、vue-hot-reload-api、vue-invalidate(還在升級中)等。
  • 第三方庫。
  • UI組件庫,比如 vux(目前計劃是11月發布適配 2.0 的版本)、餓了么前端提供的那些(並沒有給出更新計劃)。
  • 組件間通訊,不能再使用 dispatch,而需要使用全局 eventbus 或者 vuex。
  • 各種 API 廢棄,見 issue 2873,像是 attached、activated 這些生命周期 API 都被幹掉了。

具體一點的話,像是

index

$index 現在必須使用 index 了(在 v-for 中顯示聲明)

filters

不能像以前那樣到處用了,只在 {{ }} 中生效,轉而用計算屬性或者方法吧。

transition

transition 屬性被廢棄了。可以看看新的 Transitions 文檔。

vue router

加了全局和離開當前頁面的鉤子,router-link,router data,等等。

等等等等,要升級還是挺痛苦的。啊,對了 vuex 也升級到 2.0 了。更像 redux 了。心情很複雜

尾聲

差不多也就是這樣了。如果你是一個有一定經驗並懂得基本 HTML 和 CSS 的高級工程師,我相信幾天你就能看完並上手它了,畢竟對比 React 那一整套東西,還是相對簡單的。


推薦閱讀:

ECMAScript中的對象和DOM BOM對象是一個概念么?
那些你不知道的爬蟲反爬蟲套路
寫給在迷茫中前行的前端學習/工作者
解鎖緩存新姿勢——更靈活的 Cache

TAG:Vuejs | 前端开发 | 前端入门 |