Vue主要通过v-if和v-for两个指令来控制DOM元素的生成、移除和批量展示。
v-if
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>
//当awesome是假值时,h1标签将被移除
用 v-else 添加“else 块”:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no </h1>
集合 template
因为 v-if 是一个指令,所以必须,也只能将它添加到一个元素上。
那么如果想用一个v-if同时控制多个元素呢?
可以用一个 元素当做外部包裹元素,将多个需要被控制的元素包裹起来,并在上面使用 v-if。
我们知道,HTML5版本中带来的标签,实际上是一个不会被渲染的标签,所以最终的结果将不包含 元素。
<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
v-else-if,顾名思义,充当 v-if 的“else-if 块”,并且可以连续使用:
<div v-if="type === 'A'">A
</div>
<div v-else-if="type === 'B'">B
</div>
<div v-else-if="type === 'C'">C
</div>
<div v-else>Not A/B/C
</div>
与 v-else 的用法类似,v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后
v-show
除了v-if,还有一个用于条件性展示元素的指令: v-show 。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS属性display。
v-if 与v-show的区别
- v-if 是“真正”的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件恰当地被销毁和重建。(通俗地说,就是DOM会被真正地创建和删除)
- v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。(也就是说,如果一开始为假,v-if根本不创建DOM)
- v-show 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。(不管开始是真是假,DOM都会被创建,只是有可能隐藏起来而已)
- 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
- 另外,v-show 不支持 元素,也不支持 v-else。
v-for
v-for用于将一个数据数组循环渲染成一组同样的DOM元素。循环的是数组,生成的是HTML元素。
v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
<ulid="list"><liv-for="item in items">{{item.message}}</li>
</ul>
const app = Vue.createApp({data(){return {items: [{message: "foo"},{message: "bar"},]}}})app.mount("#list")
在 v-for 块中,我们可以访问所有父作用域的属性。
v-for 还支持一个可选的第二个参数,即当前元素项的索引。
<ulid="list"><liv-for="(item, index) in items">{{parentMessage}}-{{index}}-{{item.message}}</li>
</ul>
const app = Vue.createApp({data(){return {parentMessage: 'Parent',items: [{message: "foo"},{message: 'bar'},]}}
})app.mount("#list")
在 v-for 里使用对象
代码实例
<ul id="app"><liv-for="value in MyObject">{{value}}</li><p>-------------------------------------------------------------</p><liv-for="(value, key) in MyObject">{{key}}: {{value}}</li><p>-------------------------------------------------------------</p><liv-for="(value, key, index) in MyObject">{{index}} - {{key}} : {{value}}</li></ul>
const app = Vue.createApp({data(){return {MyObject: {title: "How to do lists in Vue",author: "Jane",publishedAt: "2013-10-2"}}}
})app.mount("#app")
渲染效果
维护列表各项的统一性
当 Vue 准备更新一个使用 v-for 指令渲染出来的的元素列表时,它默认使用“就地更新”的策略。
也就是说如果列表项的顺序被改变,Vue 却不会移动 DOM 元素来匹配新的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
这个默认的策略是出于高效刷新DOM的目的,但只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出(也就是列表各项没有什么差别)。
但大多数情况下,我们还是要区别列表中的各个项的,不能直接复用。
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,我们可以为每项提供一个唯一 的key 属性(要绑定):
<div v-for="item in items" :key="item.id"><!-- content -->
</div>
实际上,Vue官方将唯一状态设定为默认状态不香吗?高效刷新固然重要,但列表项几乎都是不同的,需要id唯一识别。
key属性是 Vue 识别节点的一个通用机制,并不仅仅用于 v-for ,它还具有其它用途。
不要使用,对象或数组之类的可变数据类型,作为 v-for 的 key,请用字符串或数值之类的不可变类型。
数组更新检测
v-for不仅仅是用于循环生成列表,同时监测数据来源的变化。如果数据数据发生了变化,列表项的内容也会跟着变化。
变更方法
如果你对数组型数据执行了下面的方法,使用v-for生成的列表项也会跟着变化:
- puhs()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
我们可以打开控制台,然后对前面例子中的 items 数组尝试调用变更方法。比如 example1.items.push({ message: ‘Baz’ }),可以同步观测到视图DOM元素也发生了变化。
替换数组
上面的变更方法,顾名思义,是会变更调用了这些方法的原始数组。
与之相对,数组这种JS数据类型,也有非变更方法,例如 filter()、concat() 和 slice()。它们不会变更原始数组,而总是返回一个新数组。
当使用非变更方法时,可以用新数组替换旧数组:
example1.items = example1.items.filter(item => item.message.match(/Foo/))
你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
显示过滤/排序后的结果
有时,我们想要显示一个数组经过过滤或排序后的结果,同时不实际变更或重置原始数据。
在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。
代码实例:
<divid="app"><li v-for="n in evenNumbers" :key="n">{{ n }}</li>
</div>
const app = Vue.createApp({data(){return {numbers: [1, 2, 3, 4, 5, 6]}},computed: {evenNumbers(){return this.numbers.filter(number=>number%2 === 0)}}
})app.mount("#app")
渲染效果
在计算属性不适用的情况下 (例如,在嵌套 v-for 循环中),可以使用methods方法:
<divid="app"><ulv-for="numbers in sets"><liv-for="n in even(numbers)":key="n">{{n}}</li></ul>
</div>
const app = Vue.createApp({data(){return {sets: [[1, 2, 3, 4, 5],[6, 7, 8, 9, 10],]}},methods: {even(numbers){return numbers.filter(number => number % 2 === 0)}}})app.mount("#app")
在 v-for 里使用值的范围
作为语法糖,v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div id="app" class="demo"><li v-for="n in 10" :key="n">{{ n }} </li>
</div>
const app = Vue.createApp({})app.mount("#app")
渲染效果
注意: 此处从 1 开始 并且包括 10 !!!
在 <template> 中使用 v-for
类似于 v-if,你也可以利用带有 v-for 的 来循环渲染一段包含多个元素的内容.
<divid="app"><ul><template v-for="item in items" :key="item.msg"><li>{{ item.msg }}</li></template></ul>
</div>
const app = Vue.createApp({data(){return {items: [{msg: "Hello World1"},{msg: "Hello World2"},{msg: "Hello World3"},{msg: "Hello World4"},{msg: "Hello World5"},]}}
})app.mount("#app")
渲染效果
v-for 与 v-if 一同使用
注意Vue官方不推荐在同一元素上同时使用 v-if 和 v-for.
当它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量:
<!-- 下面的代码将抛出一个异常,因为v-if无法访问todo变量 --><li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}
</li>
可以把 v-for 移动到 <template> 标签中来修改这个bug:
<template v-for="todo in todos" :key="todo.name"><li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>
在组件上使用 v-for
在自定义组件上,你可以像在任何普通元素上一样使用 v-for:
<my-component v-for="item in items" :key="item.id"></my-component>
然而,默认情况下,Vue不会自动把数据传递到组件里,因为组件有自己独立的作用域.
为了把迭代数据传递到组件里,我们需要在组件中声明并使用 props:
<my-componentv-for="(item, index) in items":item="item":index="index":key="item.id"
></my-component>
不自动将 item 注入到组件里的原因是,这会使得组件与 v-for 的运作紧密耦合。明确组件数据的来源能够使组件在其他场合重复使用。
下面是一个简单的 todo 列表的完整例子:
<divid="app"><formv-on:submit.prevent="addNewToDo"><labelfor="new-todo">add a todo</label><inputv-model="newToDoText"id="new-todo"placeholder="E.g. Feed the cat"><button>Add</button></form><ul><todo-itemv-for="(todo, index) in todos":key="todo.id":title="todo.title"@remove="todos.splice(index, 1)"></todo-item></ul>
</div>
const app = Vue.createApp({data(){return {newToDoText: '',todos: [{id: 1, title: 'Hello World 1'},{id: 2, title: 'Hello World 2'},{id: 3, title: 'Hello World 3'},{id: 4, title: 'Hello World 4'},{id: 5, title: 'Hello World 5'},],nextTodoId: 5,}},methods: {addNewToDo(){this.todos.push({id: this.nextTodoId++,title: this.newToDoText})this.newToDoText = ''}},})app.component('todo-item', {template: `<li>{{ title }}<button @click="$emit('remove')">Remove</button></li>`,props: ['title'],emits: ['remove']})app.mount("#app")
渲染效果
这里可以在输入框中输入内容, 待办内容可以添加到下面, 可以通过 Remove 按钮来移除内容.