Vue.js模板语法

在之前的章节中其实已经涉及过部分模板语法,例如 @click{{msg}} 等,模板语法是逻辑和视图之间沟通的桥梁,使用模板语法编写的 HTML 会响应 Vue 实例中的各种变化。简单来说,Vue 实例中可以随心所欲地将相关内容渲染在页面上,模板语法功不可没。有了模板语法,可以让用户界面渲染的内容和用户交互操作的代码更具有逻辑性。

Vue 模板语法是 Vue 中常用的技术之一,它的具体功能就是让与用户界面渲染和用户交互操作的代码经过一系列的编译,生成 HTML 代码,最终输出到页面上。但是,在底层的实现上,Vue 将模板编译成 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

Vue.js 使用了基于 HTML 的模板语法,允许以声明方式将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,因此可以被遵循规范的浏览器和 HTML 解析器所解析。

插值表达式

下面来看一段简单的代码,如示例代码2-2-1所示。

示例代码2-2-1 插值表达式的示例
<template>
    <div id="app">
        {{ message }}
    </div>
</template>

上面的实例代码中出现的 {{message}} 在之前的章节中也出现过多次,它的正式名称叫作插值(Mustache)表达式,也叫作插值标签。它是 Vue 模板语法中最重要的,也是最常用的,使用两个大括号 “{{}}” 来包裹,在渲染时会自动对里面的内容进行解析。Vue 模板中的插值常见的使用方法有:文本原始HTML属性JavaScript 表达式指令修饰符 等。

文本插值(v-text)

所谓文本插值,就是一对大括号中的数据经过编译和渲染出来是一个普通的字符串文本。同时,message 在这里也形成了一个数据绑定,无论何时,绑定的数据对象上的 message 属性发生了改变,插值处的内容都会实时更新。文本插值表达式的使用如示例代码2-2-2所示。

示例代码2-2-2 文本插值表达式的使用
<div id="app">
    {{ message }}
</div>
// 等同于
<div id="app" v-text="message"></div>

<div> 中的内容会被替换成 message 的内容,同时实时更新体现了双向绑定的作用。但是,也可以通过设置 v-once 指令,使得数据改变时,插值处的内容不会更新。不过,注意这会影响该节点上所有的数据绑定。

Vue 中给 DOM 元素添加 v-*** 形式的属性的写法叫作指令,v-once 指令的运用如示例代码2-2-3所示。

示例代码2-2-3 v-once指令的运用
<div id="app" v-once>
这个将不会改变:{{ message }}
</div>

v-once 仅渲染元素和组件一次,并跳过之后的更新。

原始HTML插值(v-html)

一对大括号会将数据解析为普通文本,而不是 HTML 代码。为了输出真正的 HTML 代码,需要使用 v-html 指令,如示例代码2-2-4所示。

示例代码2-2-4 v-html指令
Vue.createApp({
    data() {
        return {
            rawHtml: "<div>html文本<span>abc</span></div>"
        }
    }
}).mount("#app")
<p>{{ rawHtml }}</p>
<p>v-html: <span v-html="rawHtml"></span></p>

上面的代码中,rawHtml 是一段含有 HTML 代码的字符串,直接使用 {{rawHtml}} 并不会解析 HTML 字符串的内容,而是原模原样地显示在页面上。但是,如果使用 v-html 指令,则会作为一段 HTML 代码插入当前这个 <span> 中。如果 rawHtml 中还含有一些插值表达式或者指令,那么 v-html 会忽略解析属性值中的数据绑定。例如这样设置:

data() {
    return {
        rawHtml: "<div>html文本<span>{{abc}}</span></div>"
    }
}

需要注意的是,网页中动态渲染任意的 HTML 可能非常危险,很容易导致 XSS 攻击,请只对可信的内容使用 v-html 指令,绝不要对用户输入的内容使用这个指令。

属性插值

插值语法不能作用在 HTML 的属性上,遇到这种情况应该使用 v-bind 指令。例如,若想给 HTMLstyle 属性动态绑定数据,使用插值可能有这样的写法,如示例代码2-2-5所示。

data() {
    return {
        str: "#000000"
    }
}
<div id="app" style="color:{{str}}"></div>

这样写的插值是无法生效的,也就是 Vue 无法识别写在 HTML 属性上的插值表达式,那么遇到这种情况,可以采用 v-bind 指令,如示例代码2-2-6所示。

示例代码2-2-6 插值和HTML属性2
data() {
    return {
        str: "color:#000000"
    }
}
<div id="app" v-bind:style="str"></div>

对于布尔属性(它们只要存在,就意味着值为 true),v-bind 工作起来略有不同,在这个例子中为:

<button v-bind:disabled="isButtonDisabled">Button</button>

如果 isButtonDisabled 的值是 nullundefinedfalse,则 disabled 属性甚至不会出现在渲染出来的 <button> 元素中。

JavaScript 表达式插值

在之前讲解的插值表达式中,基本上都是一直只绑定简单的属性键值,例如直接将 message 的值显示出来。但是实际情况是,对于所有的数据绑定,Vue.js 都提供了完整的 JavaScript 表达式支持,如示例代码2-2-7所示。

示例代码2-2-7 JavaScript表达式
// 单目运算
{{ number + 1 }}
// 三目运算
{{ ok ? 'YES' : 'NO' }}
// 字符串处理
{{ message.split('').reverse().join('') }}
// 拼接字符串
<div v-bind:id="'list-' + id"></div>

例如加法运算、三目运算、字符串的拼接以及常用的 split 处理等,这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 代码被解析。

指令

传统意义上的指令就是指挥机器工作的指示和命令,Vue 中的指令是指带有 v- 前缀或者说以 v- 开头的、设置在 HTML 节点上的特殊属性。

指令的作用是,当表达式的值改变时,将其产生的连带影响以响应的方式作用在 DOM 上。之前用到的 v-bindv-model 都属于指令,它们都属于 Vue 中的内置指令,与之相对应的叫作自定义指令。下面就讲解一下 Vue 中主要的内置指令。

v-bind

v-bind 指令可以接受参数,在 v-bind 后面加上一个冒号再跟上参数,这个参数一般是 HTML 元素的属性,如示例代码2-2-8所示。

示例代码2-2-8 v-bind 指令

<a v-bind:href="url">...</a>
<img v-bind:src="url" />

使用 v-bind 绑定 HTML 元素的属性之后,这个属性就有了数据绑定的效果,在 Vue 实例的 data 中定义之后,就会直接替换属性的值。

v-bind 还有一个简写的用法就是直接用冒号而省去 v-bind,代码如下:

<img :src="url" />

使用 v-binddata 结合可以很便捷地实现数据渲染,但是要注意,并不是所有的数据都需要设置到 data 中,当一些组件中的变量与显示无关或者没有相关的数据绑定逻辑时,也无须设置在 data 中,在 methods 中使用局部变量或者其它地方。这样可以减少 Vue 对数据的响应式监听,从而提升性能。

v-bind 可以绑定来自以下几种地方的数据:

  • data 属性

    <template>
      <div>
        <button v-bind:title="buttonTitle">Hover over me</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          buttonTitle: 'This is a button'
        };
      }
    };
    </script>
  • computed 属性

    <template>
      <div>
        <button v-bind:title="computedTitle">Hover over me</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          baseTitle: 'This is a button'
        };
      },
      computed: {
        computedTitle() {
          return `${this.baseTitle} - Hover for more info`;
        }
      }
    };
    </script>
  • methods 中的返回值

    <template>
      <div>
        <button v-bind:title="getButtonTitle">Hover over me</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          baseTitle: 'This is a button'
        };
      },
      methods: {
        getButtonTitle() {
          return `${this.baseTitle} - Dynamic Title`;
        }
      }
    };
    </script>
  • props

    <template>
      <div>
        <button v-bind:title="titleFromParent">Hover over me</button>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        titleFromParent: String
      }
    };
    </script>

v-if、v-else 和 v-else-if

这 3 个指令与编写代码时使用的 if/else 语句是一样的,一般是搭配起来使用,只有 v-if 可以单独使用,v-elsev-else-if 必须搭配 v-if 来使用。

这些指令执行的结果是根据表达式的值 “真或假” 来渲染元素。在切换时,元素及其组件与组件上的数据绑定会被销毁并重建,如示例代码2-2-9所示。

示例代码2-2-9 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>

需要再强调一下,如果 v-if 的值是 false,那么 v-if 所在的 HTMLDOM 节点及其子元素都会被直接移除,这些元素上面的事件和数据绑定也会被移除。

v-show

v-if 类似,v-show 也用于控制一个元素是否显示,但是与 v-if 不同的是,如果 v-if 的值是 false,则这个元素会被销毁,不在 DOM 中。但是,v-show 的元素会始终被渲染并保存在 DOM 中,它只是被隐藏,显示和隐藏只是简单地切换 CSSdisplay 属性,如示例代码2-2-10所示。

示例代码2-2-10 v-show指令
<div v-show="type === 'A'">
A
</div>

Vue 中,并没有 v-hide 指令,可以用 v-show="!xxx" 来代替。

一般来说,v-if 切换开销更高,而 v-show 的初始渲染开销更高。因此,如果需要非常频繁地切换,使用 v-show 更好;如果在运行时条件很少改变,则使用 v-if 更好。

v-for

与代码中的 for 循环功能类似,可以用 v-for 指令通过一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名,如示例代码2-2-11所示。

示例代码2-2-11 v-for 指令
<ul>
    <li v-for="item in items">
        {{ item.message }}
    </li>
</ul>
Vue.createApp({
    data() {
        return {
            items: [
                { message: "Jack" },
                { message: "Tom" }
            ]
        }
    }
}).mount("#app")

渲染结果如图2-3所示。

image 2024 02 21 12 25 24 604
Figure 1. 图2-3 v-for 一般用法的演示结果

也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

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

在使用 v-for 指令时,如果我们在 data 中定义的数组动态地改变,那么执行 v-for 所渲染的结果也会改变,这也是 Vue 中响应式的体现,例如我们对数组进行 push()pop()shift()unshift()splice()sort()reverse() 操作时,渲染结果也会动态地改变,如示例代码2-2-11所示。

示例代码2-2-11 v-for 指令(续)
<div id="app">
    <button @click="add">add</button>
    <ul>
        <li v-for="item in items">
            {{ item.message }}
        </li>
    </ul>
</div>
Vue.createApp({
    data() {
        return {
            items: [
                { message: "Jack" },
                { message: "Tom" }
            ]
        }
    },
    methods:{
        add(){
            this.items.push({message: "Amy"})
        }
    }
}).mount("#app")

v-for 代码区块中,可以访问当前 Vue 实例的所有其他属性,也就是其他设置在 data 中的值。v-for 还支持一个可选的第二个参数,即当前项的索引,如示例代码2-2-12所示。

示例代码2-2-12 v-for 指令的参数
<ul id="app">
    <li v-for="(item, index) in items">
        {{ parentMessage }} - {{ index }} - {{ item.message }}
    </li>
</ul>
Vue.createApp({
    data() {
        return {
            parentMessage: "Parent",
            items: [
                { message: "Jack" },
                { message: "Tom" }
            ]
        }
    }
}).mount("#app")

渲染结果如图2-4所示。

image 2024 02 21 12 30 49 462
Figure 2. 图2-4 v-for 通过索引存取数据项的演示结果

v-for 指令不仅可以遍历一个数组,还可以遍历一个对象,功能就像 JavaScript 中的 for/inObject.keys() 一样,如示例代码2-2-13所示。

示例代码2-2-13 v-for指令遍历对象
<ul id="app">
    <li v-for="value in object">
        {{ value }}
    </li>
</ul>
Vue.createApp({
    data() {
        return {
            object: {
                title: "Big Big",
                author: "Jack",
                time: "2019-04-10"
            }
        }
    }
}).mount("#app")

渲染结果如图2-5所示。

image 2024 02 21 12 33 13 987
Figure 3. 图2-5 v-for遍历对象的演示结果

和使用索引一样,v-for 指令提供的第二个参数为 property 名称(也就是键名),第三个参数为 index 索引,如示例代码2-2-14所示。

示例代码2-2-14 v-for 指令的键名
<ul id="app">
    <li v-for="(value,name,index) in object">
        {{ index }}:{{ name }}  {{ value }}
    </li>
</ul>
Vue.createApp({
    data() {
        return {
            object: {
                title: "Big Big",
                author: "Jack",
                time: "2019-04-10"
            }
        }
    }
}).mount("#app")

渲染结果如图2-6所示。

image 2024 02 21 12 38 53 104
Figure 4. 图2-6 v-for 显示键名索引的演示结果

在使用 Object.keys() 遍历对象时,有时遍历出来的键(Key)的顺序并不是我们定义时的顺序,比如定义时 title 在第一个,author 在第二个,time 在第三个,但是遍历出来却不是这个顺序(这里只是举一个例子,上面代码的应用场景是按照顺序来的)。

需要注意的是,在使用 v-for 遍历对象时,是按照调用 Object.keys() 的结果顺序遍历的,所以在某些情况下并不会按照定义对象的顺序来遍历。若想严格控制顺序,则要在定义时转换成数组来遍历。

为了让 Vue 可以跟踪每个节点,则需要为每项提供一个唯一的 key 属性。如示例代码2-2-15所示。

示例代码2-2-15 v-for key 属性
<div v-for="item in items" v-bind:key="item.id">
    <!-- 内容 -->
</div>

Vue 更新使用了 v-for 渲染的元素列表时,它会默认使用 “就地更新” 的策略。如果数据项的顺序被改变了,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染到用户界面上。

Vue 会尽可能地对组件进行高度复用,所以增加 key 可以标识组件的唯一性,目的是为了更好地区别各个组件,key 更深层的意义是为了高效地更新虚拟 DOM。关于虚拟 DOM 的概念,可以简单理解成 Vue 在每次把数据更新到用户界面时,都会在内部事先定义好前后两个虚拟的 DOM,一般是对象的形式。通过对比前后两个虚拟 DOM 的异同来针对性地更新部分用户界面,而不是整体更新(没有改变的用户界面部分不去修改,这样可以减少 DOM 操作,提升性能)。设置 key 值有利于 Vue 更高效地查找需要更新的用户界面。不要使用对象或数组之类的非基本类型值作为 v-forkey,请用字符串或数字类型的值。

v-for 指令和 v-if 指令本身是不推荐使用在同一个元素上的,代码如下:

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

我们在日常开发时,在列表渲染时经常会遇到这种场景,以前在 Vue 2 版本中采用上面的写法并不会报错,然而在 Vue 3 中这样写会报错,如图2-7所示。

image 2024 02 21 12 44 50 179
Figure 5. 图2-7 错误截图

因为它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 中的变量 todo,可以将 v-for 指令写在一个空的元素 <template> 上来达到循环效果,同时将 v-if 指令写在 <li> 上来达到是否渲染的效果,这样就不会报错了,代码如下:

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

v-on

在之前的章节中也使用过 v-on 指令,这个指令主要用来给 HTML 元素绑定事件,是 Vue 中用得最多的指令之一。v-on 的冒号后面可以跟一个参数,这个参数就是触发事件的名称,v-on 的值可以是一个方法的名字或一个内联语句。和 v-bind 一样,v-on 指令可以省略 v-on:,而用 @ 来代替,如示例代码2-2-16所示。

示例代码2-2-16 v-on 指令
<div id="app">
    <button @click="clickCallback">点我</button>
</div>
Vue.createApp({
    methods:{
        clickCallback(event) {
            console.log('click')
        }
    }
}).mount("#app")

在上面的代码中,将 v-on 指令应用于 click 事件上,同时给了一个方法名 clickCallback 作为事件的回调函数,当 DOM 触发 click 事件时会进入在 methods 中定义的 clickCallback 方法中。event 参数是当前事件的 Event 对象。

如果想在事件中传递参数,可以采用内联语句,该语句可以访问一个 $event 属性,如示例代码2-2-17所示。

示例代码2-2-17 v-on 指令click事件传递参数
<div id="app">
    <button @click="clickCallback('hello',$event)">点我</button>
</div>
Vue.createApp({
    methods:{
        clickCallback(params,event) {
            console.log(params,event)
        }
    }
}).mount("#app")

v-on 指令用在普通元素上时,只能监听原生 DOM 事件,例如 click 事件、touch 事件等,用在自定义元素组件上时,也可以监听子组件触发的自定义事件,如示例代码2-2-18所示。

示例代码2-2-18 v-on 指令自定义事件传递参数
<cuscomponent @cusevent="handleThis"></cuscomponent>
<!-- 内联语句 -->
<cuscomponent @cusevent="handleThis(123, $event)"></cuscomponent>

自定义事件一般用在组件通信中,我们会在后面的章节讲解,在使用 v-on 监听原生 DOM 事件时,可以添加一些修饰符并有选择性地执行一些方法或者程序逻辑:

  • .stop:阻止事件继续传播,相当于调用 event.stopPropagation()

  • .prevent:告诉浏览器不要执行与事件关联的默认行为,相当于调用 event.preventDefault()

  • .capture:使用事件捕获模式,即元素自身触发的事件先在这里处理,然后才交由内部元素进行处理。

  • .self:只有当 event.target 是当前元素自身才触发处理函数。

  • .once:事件只会触发一次。

  • .passive:告诉浏览器不阻止与事件关联的默认行为,相当于不调用 event.preventDefault()。与 prevent 相反。

  • .left.middle.right:分别对应鼠标左键、中键、右键的单击触发。

  • .{keyAlias}:只有当事件是由特定按键触发时才触发回调函数。

下面举一个使用 .prevent 的例子,如示例代码2-2-19所示。示例代码2-2-19 v-on 指令修饰符

<div id="app">
    <a @click.prevent="clickCallback" href="https://www.qq.com">点我</a>
</div>
Vue.createApp({
    methods:{
        clickCallback(event) {
            // 相当于在这里调用了event.preventDefault()方法
            console.log(event)
        }
    }
}).mount("#app")

对于 <a> 标签而言,它的浏览器默认事件行为就是单击后打开 href 属性所配置的链接,设置了 .prevent 修饰符之后,就相当于在 click 回调方法中首先调用了 event.preventDefault() 方法,当单击 <a> 标签时就只会触发 @click 所绑定的事件,不会再触发默认事件了。

vuev-on 是可以绑定多个方法:

  • v-on 绑定多个方法(采用的是对象形式)

    <button v-on="{click: clickMethds, mousemove: mouseMethods}">按钮<button>
  • 一个事件绑定多个方法

    <button @click="click1,click2"></button>

v-model

最后讲解一下 v-model 指令,一般用在表单元素上,例如 <input type="text"/><input type="checkbox" /><input type="radio" /><select> 等和 自定义组件,以便实现双向绑定。v-model 会忽略所有表单元素的 valuecheckedselected 属性的初始值,因为它选择 Vue 实例中 data 设置的数据作为具体的值,如示例代码2-2-20所示。

示例代码2-2-20 v-model指令
<div id="app">
    <input v-model="message">
        <p>hello {{message}}</p>
</div>
Vue.createApp({
    data() {
        return {message:'Jack'}
    }
}).mount("#app")

在这个例子中,直接在浏览器 <input> 中输入别的内容,下面的 <p> 中的内容会跟着变化。这就是双向数据绑定。

v-model 应用在表单输入元素上时,Vue 内部会为不同的输入元素使用不同的属性并触发不同的事件:

  • texttextarea 使用 value 属性和 input 事件。

  • checkboxradio 使用 checked 属性和 change 事件。

  • select 字段将 value 作为属性并将 change 作为事件。

下面的例子是 v-modelv-for 结合实现 <select> 的双向数据绑定,如示例代码2-2-21所示。

示例代码2-2-21 v-model 结合 v-for
<div id="app">
    <select v-model="selected">
        <option v-for="option in options" v-bind:value="option.value">
            {{ option.text }}
        </option>
    </select>
    <span>Selected: {{ selected }}</span>
</div>
Vue.createApp({
    data() {
        return {
            selected: 'Jack',
            options: [
                { text: 'PersonOne', value: 'Jack' },
                { text: 'PersonTwo', value: 'Tom' },
                { text: 'PersonThree', value: 'Leo' }
            ]
        }
    }
}).mount("#app")

渲染效果如图2-8所示。

image 2024 02 21 13 01 44 580
Figure 6. 图2-8 v-model 和 <select> 使用的演示结果

在切换 <select> 时,页面上的值会动态地改变,这就是结合 <select> 的表现。另外,在文本区域 <textarea>,直接使用插值表达式是不会有双向绑定效果的,代码如下:

<textarea>{{text}}</textarea>

这时需要使用 v-model 来代替,代码如下:

<textarea v-model="text"></textarea>

若想单独给某些 input 输入元素绑定值,而不想要双向绑定的效果,则可以直接用 v-bind 指令给 value 赋值,代码如下:

<input v-bind:value="text"></input>

使用 v-model 时,可以添加一些修饰符来有选择性地执行一些方法或者程序逻辑:

  • .lazy:在默认情况下,v-model 会同步输入框中的值和数据。可以通过这个修饰符,转变为在输入框的 change 事件中再进行值和数据同步。

  • .number:自动将用户的输入值转化为 number 类型。

  • .trim:自动过滤用户输入的首尾空格。

v-model 指令也可以绑定给自定义的 Vue 组件使用,在后文将具体讲解。

默认情况下,v-model 在用户输入事件发生时(例如在输入框中输入内容时)会立即更新数据。然而,有时你可能希望延迟更新数据,直到用户完成输入。为此,Vue 提供了 .lazy 修饰符。

当你在 v-model 指令上使用 .lazy 修饰符时,数据绑定将不再在 input 事件上进行,而是改为在 change 事件上进行。这意味着数据将仅在输入框失去焦点或按下回车键时更新。

TODO 自定义组件的 v-model

v-memo

v-memo 用于告诉 Vue 仅在某些依赖发生变化时才重新渲染某个 DOM 元素或组件。当指定的条件不发生变化时,Vue 会跳过该部分的重新渲染,从而节省计算资源。

v-memoVue 3 中引入的指令,它的作用是在列表渲染时,在某种场景下跳过新的虚拟 DOM 的创建提升性能,使用方法如示例代码2-2-22所示。

示例代码2-2-22 v-memo 指令
<div v-memo="[valueA, valueB]">
...
</div>

当组件重新渲染的时候,如果 valueAvalueB 都维持不变,那么对这个 <div> 以及它的所有子节点的更新都将被跳过。事实上,即使是虚拟 DOMVNode 创建也将被跳过,因为子树的记忆副本可以被重用。

v-memo 指令主要结合 v-for 一起使用,而且必须作用在同一个元素上,如示例代码2-2-23所示。

<div id="app">
    <button @click="selected = '3'">点我</button>
    <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
        <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
    </div>
</div>
Vue.createApp({
    data() {
        return {
            selected: '1',
            list: [
                { id: '1'},
                { id: '2'},
                { id: '3'},
                { id: '4'},
            ]
        }
    }
}).mount("#app")

在这个例子中,只有以下 div 会被重新渲染:

  • item.id === '1'(初始值为 true,现在为 false,所以需要重新渲染)。

  • item.id === '3'(初始值为 false,现在为 true,所以需要重新渲染)。

  • item.id === '2' 和 item.id === '4' 在两次渲染之间没有变化,因此它们的 div 不会被重新渲染。

在之前讲解 v-for 指令时,我们知道 key 属性给每个列表元素分配了唯一的键值,这样使得 Vue 在做前后的虚拟 DOM 改变对比时更加高效,但前提是需要创建新的虚拟 DOM,当我们使用了 v-memo 时,如果当前的列表元素所对应的 v-memo 没有改变,那么这部分虚拟 DOM 也不会重新创建,减少了过多的虚拟 DOM 创建,也能在一定程度上提升性能。当然,这在列表条数很少时体现得并不明显,但是当列表很长时,也就能体现出性能差异了。

v-memo 的引入也使得在大量列表渲染方面,Vue 3 离成为最快的主流前端框架更近了一步。

v-cloak

v-cloakVue.js 提供的一个指令,用于保持 Vue 模板的占位符内容在 Vue 完全初始化之前不可见。通常,它用于在页面加载期间避免显示模板的未处理内容,直到 Vue 实例完全渲染完毕。

v-cloak 的作用:

  • 隐藏 Vue 模板内容:在 Vue 完全挂载之前,带有 v-cloak 指令的元素和它们的内容会保持隐藏。它通常与 CSS 配合使用,通过 display: none 来隐藏这些内容。

  • 解决页面闪烁问题:当你将 Vue 渲染到页面时,页面上可能会闪烁显示未处理的模板内容,尤其是在 Vue 实例加载慢或有较大资源时。使用 v-cloak 可以确保 Vue 完全加载之后,页面才显示模板内容。

<template>
    <div id="app" v-cloak>
        <p>{{ message }}</p>
    </div>
</template>
<style>
[v-cloak] {
    display: none;
}
</style>

v-once

v-onceVue.js 提供的一个指令,用于在模板中标记某部分内容只渲染一次。也就是说,使用 v-once 的元素及其子元素会在第一次渲染时渲染一次,之后即使数据变化,该部分内容也不会重新渲染。这个指令在性能优化和处理一些静态内容时非常有用。

何时使用 v-once

  • 静态内容:如果某些部分的数据或结构在应用生命周期内是固定的,不会根据状态变化,使用 v-once 可以避免不必要的重新渲染。

  • 性能优化:对于大型应用,特别是有许多内容固定的部分,使用 v-once 可以减轻 Vue 的渲染负担,提高性能,尤其是在数据频繁变化时。

v-pre

v-preVue.js 提供的一个指令,用于跳过 Vue 的编译过程,直接渲染原始 HTML 标签和内容。简单来说,v-pre 用来标记某些部分的模板内容应该被 Vue 原样渲染,而不经过 Vue 的模板编译和数据绑定。这通常用于静态内容,尤其是在模板中嵌套 HTML 或一些需要被原样渲染的字符串时。

v-slot

v-slotVue.js 提供的一个指令,用于在插槽(slot)中传递内容和动态数据。它是 Vue 2.6 及以上版本中引入的插槽语法,旨在增强插槽的灵活性和可控性。通过 v-slot,开发者可以传递数据给插槽并在父组件中动态渲染插槽内容。

插槽是 Vue 提供的一种机制,用来在父组件中传递内容到子组件中。插槽可以是默认插槽(没有名字的插槽)或具名插槽(有名字的插槽)。v-slot 指令的主要作用是让父组件控制子组件中插槽的内容。

  1. 默认插槽

    如果插槽没有名字,可以直接使用 v-slot 来绑定数据或内容。

    <!-- ParentComponent.vue -->
    <template>
      <child-component>
        <template v-slot>
          <p>这是从父组件传递过来的插槽内容。</p>
        </template>
      </child-component>
    </template>
  2. 具名插槽

    如果插槽有名字,可以通过 v-slot:name 来指定插槽的名称。

    <!-- ParentComponent.vue -->
    <template>
      <child-component>
        <template v-slot:header>
          <h1>这是头部插槽内容</h1>
        </template>
        <template v-slot:footer>
          <p>这是底部插槽内容</p>
        </template>
      </child-component>
    </template>
  3. 插槽作用域

    通过 v-slot 可以将子组件的某些数据传递给父组件的插槽,形成插槽的 “作用域”。子组件将数据暴露给父组件,父组件可以通过作用域来访问这些数据。

    <!-- ChildComponent.vue -->
    <template>
      <div>
        <slot :message="message"></slot>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          message: '来自子组件的数据'
        }
      }
    }
    </script>
    <!-- ParentComponent.vue -->
    <template>
      <child-component>
        <template v-slot:default="slotProps">
          <p>{{ slotProps.message }}</p>
        </template>
      </child-component>
    </template>

    在上面的例子中,子组件通过 slotmessage 作为属性暴露给插槽。父组件通过 v-slot:default="slotProps" 访问这个 message 属性,并在插槽中显示它。

在 Vue 2.6 版本及以上,你可以使用更简洁的语法,去掉 template 标签。如果插槽没有作用域数据,也可以不写 v-slot

<child-component v-slot:header>
  <h1>这是头部内容</h1>
</child-component>

<child-component v-slot>
  <p>这是默认插槽内容</p>
</child-component>

指令的动态参数

在使用 v-bind 或者 v-on 指令时,冒号后面的字符串被称为指令的参数,代码如下:

<a v-bind:href="url">...</a>

这里 href 是参数,告知 v-bind 指令将该元素的 href 属性与表达式 url 的值绑定。

<a v-on:click="doSomething">...</a>

在这里 click 是参数,告知 v-on 指令绑定哪种事件。

把用方括号括起来的 JavaScript 表达式作为一个 v-bindv-on 指令的参数,这种参数被称为动态参数。

v-bind 指令的动态参数代码如下:

<a v-bind:[attributeName]="url"> ... </a>

代码中的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果 Vue 实例有一个 data 属性 attributeName,其值为 href,那么这个绑定将等价于 v-bind:href

v-on 指令的动态参数代码如下:

<button v-on:[event]="doThis"></button>

代码中的 event 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果 Vue 实例有一个 data 属性 event,其值为 click,那么这个绑定将等价于 v-on:click

动态参数表达式有一些语法约束,因为某些字符(例如空格和引号)放在 HTML 属性名里是无效的,所以要尽量避免使用这些字符。例如,下面的代码在参数中添加了空格,所以是无效的:

<!-- 这会触发一个编译警告 -->
<a v-bind:['foo' + bar]="value"> ... </a>

变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂的表达式。另外,如果在 DOM 中使用模板(直接在一个 HTML 文件中编写模板需要回避大写键名),需要注意浏览器会把属性名全部强制转为小写 ,代码如下:

<!-- 在 DOM 中使用模板时这段代码会被转换为 'v-bind:[someattr]' -->
<a v-bind:[someAttr]="value"> ... </a>

至此,与 Vue.js 模板语法有关的内容就讲解完了。模板语法是逻辑和视图之间沟通的桥梁,是 Vue 中实现页面逻辑最重要的知识,也是用得最多的知识,希望读者掌握好这部分知识,为后面 Vue 其他相关知识的学习打下坚实的基础。