列表渲染
可以用 v-for 指令根據(jù)一組數(shù)組的選項(xiàng)列表進(jìn)行渲染。 v-for 指令需要以 item in items 形式的特殊語(yǔ)法, items 是源數(shù)據(jù)數(shù)組并且 item 是數(shù)組元素迭代的別名。
目錄
用 v-for
把一個(gè)數(shù)組對(duì)應(yīng)為一組元素
我們可以用 v-for
指令基于一個(gè)數(shù)組來(lái)渲染一個(gè)列表。v-for
指令需要使用 item in items
形式的特殊語(yǔ)法,其中 items
是源數(shù)據(jù)數(shù)組,而 item
則是被迭代的數(shù)組元素的別名。
<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' } ] } })
結(jié)果:
在 v-for
塊中,我們可以訪問(wè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' } ] } })
結(jié)果:
你也可以用 of
替代 in
作為分隔符,因?yàn)樗咏?JavaScript 迭代器的語(yǔ)法:
<div v-for="item of items"></div>
在 v-for
里使用對(duì)象
你也可以用 v-for
來(lái)遍歷一個(gè)對(duì)象的屬性。
<ul id="v-for-object" class="demo"> <li v-for="value in object"> {{ value }} </li> </ul>
new Vue({ el: '#v-for-object', data: { object: { title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2016-04-10' } } })
結(jié)果:
你也可以提供第二個(gè)的參數(shù)為 property 名稱 (也就是鍵名):
<div v-for="(value, name) in object"> {{ name }}: {{ value }} </div>
還可以用第三個(gè)參數(shù)作為索引:
<div v-for="(value, name, index) in object"> {{ index }}. {{ name }}: {{ value }} </div>
在遍歷對(duì)象時(shí),會(huì)按
Object.keys()
的結(jié)果遍歷,但是不能保證它的結(jié)果在不同的 JavaScript 引擎下都一致。
維護(hù)狀態(tài)
當(dāng) Vue 正在更新使用 v-for
渲染的元素列表時(shí),它默認(rèn)使用“就地更新”的策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue 將不會(huì)移動(dòng) DOM 元素來(lái)匹配數(shù)據(jù)項(xiàng)的順序,而是就地更新每個(gè)元素,并且確保它們?cè)诿總€(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
屬性:
<div v-for="item in items" v-bind:key="item.id"> <!-- 內(nèi)容 --> </div>
建議盡可能在使用 v-for
時(shí)提供 key
attribute,除非遍歷輸出的 DOM 內(nèi)容非常簡(jiǎn)單,或者是刻意依賴默認(rèn)行為以獲取性能上的提升。
因?yàn)樗?Vue 識(shí)別節(jié)點(diǎn)的一個(gè)通用機(jī)制,key
并不僅與 v-for
特別關(guān)聯(lián)。后面我們將在指南中看到,它還具有其它用途。
不要使用對(duì)象或數(shù)組之類的非基本類型值作為
v-for
的key
。請(qǐng)用字符串或數(shù)值類型的值。
更多 key
attribute 的細(xì)節(jié)用法請(qǐng)移步至 key
的 API 文檔。
數(shù)組更新檢測(cè)
變異方法 (mutation method)
Vue 將被偵聽(tīng)的數(shù)組的變異方法進(jìn)行了包裹,所以它們也將會(huì)觸發(fā)視圖更新。這些被包裹過(guò)的方法包括:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
你可以打開(kāi)控制臺(tái),然后對(duì)前面例子的 items
數(shù)組嘗試調(diào)用變異方法。比如 example1.items.push({ message: 'Baz' })
。
替換數(shù)組
變異方法,顧名思義,會(huì)改變調(diào)用了這些方法的原始數(shù)組。相比之下,也有非變異 (non-mutating method) 方法,例如 filter()
、concat()
和 slice()
。它們不會(huì)改變?cè)紨?shù)組,而總是返回一個(gè)新數(shù)組。當(dāng)使用非變異方法時(shí),可以用新數(shù)組替換舊數(shù)組:
example1.items = example1.items.filter(function (item) { return item.message.match(/Foo/) })
你可能認(rèn)為這將導(dǎo)致 Vue 丟棄現(xiàn)有 DOM 并重新渲染整個(gè)列表。幸運(yùn)的是,事實(shí)并非如此。Vue 為了使得 DOM 元素得到最大范圍的重用而實(shí)現(xiàn)了一些智能的啟發(fā)式方法,所以用一個(gè)含有相同元素的數(shù)組去替換原來(lái)的數(shù)組是非常高效的操作。
注意事項(xiàng)
由于 JavaScript 的限制,Vue 不能檢測(cè)以下數(shù)組的變動(dòng):
當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:
vm.items[indexOfItem] = newValue
當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:
vm.items.length = newLength
舉個(gè)例子:
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是響應(yīng)性的 vm.items.length = 2 // 不是響應(yīng)性的
為了解決第一類問(wèn)題,以下兩種方式都可以實(shí)現(xiàn)和 vm.items[indexOfItem] = newValue
相同的效果,同時(shí)也將在響應(yīng)式系統(tǒng)內(nèi)觸發(fā)狀態(tài)更新:
// Vue.set Vue.set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
實(shí)例方法,該方法是全局方法 Vue.set
的一個(gè)別名:
vm.$set(vm.items, indexOfItem, newValue)
為了解決第二類問(wèn)題,你可以使用 splice
:
vm.items.splice(newLength)
對(duì)象變更檢測(cè)注意事項(xiàng)
還是由于 JavaScript 的限制,Vue 不能檢測(cè)對(duì)象屬性的添加或刪除:
var vm = new Vue({ data: { a: 1 } }) // `vm.a` 現(xiàn)在是響應(yīng)式的 vm.b = 2 // `vm.b` 不是響應(yīng)式的
對(duì)于已經(jīng)創(chuàng)建的實(shí)例,Vue 不允許動(dòng)態(tài)添加根級(jí)別的響應(yīng)式屬性。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套對(duì)象添加響應(yīng)式屬性。例如,對(duì)于:
var vm = new Vue({ data: { userProfile: { name: 'Anika' } } })
你可以添加一個(gè)新的 age
屬性到嵌套的 userProfile
對(duì)象:
Vue.set(vm.userProfile, 'age', 27)
你還可以使用 vm.$set
實(shí)例方法,它只是全局 Vue.set
的別名:
vm.$set(vm.userProfile, 'age', 27)
有時(shí)你可能需要為已有對(duì)象賦值多個(gè)新屬性,比如使用 Object.assign()
或 _.extend()
。在這種情況下,你應(yīng)該用兩個(gè)對(duì)象的屬性創(chuàng)建一個(gè)新的對(duì)象。所以,如果你想添加新的響應(yīng)式屬性,不要像這樣:
Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
你應(yīng)該這樣做:
vm.userProfile = Object.assign({}, vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
顯示過(guò)濾/排序后的結(jié)果
有時(shí),我們想要顯示一個(gè)數(shù)組經(jīng)過(guò)過(guò)濾或排序后的版本,而不實(shí)際改變或重置原始數(shù)據(jù)。在這種情況下,可以創(chuàng)建一個(gè)計(jì)算屬性,來(lái)返回過(guò)濾或排序后的數(shù)組。
例如:
<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)中) 你可以使用一個(gè)方法:
<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
里使用值范圍
v-for
也可以接受整數(shù)。在這種情況下,它會(huì)把模板重復(fù)對(duì)應(yīng)次數(shù)。
<div> <span v-for="n in 10">{{ n }} </span> </div>
結(jié)果:
在 <template>
上使用 v-for
類似于 v-if
,你也可以利用帶有 v-for
的 <template>
來(lái)循環(huán)渲染一段包含多個(gè)元素的內(nèi)容。比如:
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
v-for 與 v-if 一同使用
注意我們不推薦在同一元素上使用
v-if
和v-for
。更多細(xì)節(jié)可查閱 風(fēng)格指南。
當(dāng)它們處于同一節(jié)點(diǎn),v-for
的優(yōu)先級(jí)比 v-if
更高,這意味著 v-if
將分別重復(fù)運(yùn)行于每個(gè) v-for
循環(huán)中。當(dāng)你只想為部分項(xiàng)渲染節(jié)點(diǎn)時(shí),這種優(yōu)先級(jí)的機(jī)制會(huì)十分有用,如下:
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
上面的代碼將只渲染未完成的 todo。
而如果你的目的是有條件地跳過(guò)循環(huán)的執(zhí)行,那么可以將 v-if
置于外層元素 (或 <template>
)上。如:
<ul v-if="todos.length"> <li v-for="todo in todos"> {{ todo }} </li> </ul> <p v-else>No todos left!</p>
在組件上使用 v-for
這部分內(nèi)容假定你已經(jīng)了解組件相關(guān)知識(shí)。你也完全可以先跳過(guò)它,以后再回來(lái)查看。
在自定義組件上,你可以像在任何普通元素上一樣使用 v-for
。
<my-component v-for="item in items" :key="item.id"></my-component>
2.2.0+ 的版本里,當(dāng)在組件上使用
v-for
時(shí),key
現(xiàn)在是必須的。
然而,任何數(shù)據(jù)都不會(huì)被自動(dòng)傳遞到組件里,因?yàn)榻M件有自己獨(dú)立的作用域。為了把迭代數(shù)據(jù)傳遞到組件里,我們要使用 prop:
<my-component v-for="(item, index) in items" v-bind:item="item" v-bind:index="index" v-bind:key="item.id" ></my-component>
不自動(dòng)將 item
注入到組件里的原因是,這會(huì)使得組件與 v-for
的運(yùn)作緊密耦合。明確組件數(shù)據(jù)的來(lái)源能夠使組件在其他場(chǎng)合重復(fù)使用。
下面是一個(gè)簡(jiǎn)單的 todo 列表的完整例子:
<div id="todo-list-example"> <form v-on:submit.prevent="addNewTodo"> <label for="new-todo">Add a todo</label> <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" > <button>Add</button> </form> <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 模板時(shí)是十分必要的,因?yàn)樵?nbsp;<ul>
元素內(nèi)只有<li>
元素會(huì)被看作有效內(nèi)容。這樣做實(shí)現(xiàn)的效果與<todo-item>
相同,但是可以避開(kāi)一些潛在的瀏覽器解析錯(cuò)誤。查看 DOM 模板解析說(shuō)明 來(lái)了解更多信息。
Vue.component('todo-item', { template: '\ <li>\ {{ title }}\ <button v-on:click="$emit(\'remove\')">Remove</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 = '' } } })