# 模板 - 内容的承载体

Vue 在渲染组件的时候,需要使用者提供一个渲染模板或者渲染函数。渲染模板就是一个HTML字符串,中间加入了一些Vue特制的语法规则,这样能够让 Vue 把数据和模板进行了绑定,当数据发生变化时会让页面能够及时刷新。这节内容,我们学习一下 Vue 的模板及其本质。

# 数据绑定

通过双大括号把属性与模板进行绑定,当属性变化时页面也会实时更新。双大括号中可以是:

  • data 或 props 中声明的属性;
  • 计算属性;
  • JS 表达式;
<!-- title 可以是一个计算属性 -->
<div>{{ title }}</div>

<!-- 可接收 JS 表达式 -->
<div>{{ age + 1 }}</div>
<div>{{ age > 18 ? '大朋友' : '小朋友' }}</div>
1
2
3
4
5
6

经过编译后的渲染函数(render):

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div", null, _toDisplayString(_ctx.title), 1 /* TEXT */),
    _createCommentVNode(" 可接收 JS 表达式 "),
    _createVNode("div", null, _toDisplayString(_ctx.age + 1), 1 /* TEXT */),
    _createVNode("div", null, _toDisplayString(_ctx.age > 18 ? '大朋友' : '小朋友'), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7
8

# 指令 v-if

v-if 指令如同编程语言中的 if 条件语句,使用方法可以参考编程语言中的 if 语句,根据不同的条件来渲染不同的模板。v-if 内容部分只要是一个表达式即可,可以使用 v-else-if 和 v-else。

<!-- v-if 条件渲染 -->
<h1 v-if="age < 18">我是小朋友</h1>
<h1 v-else-if="age > 18 && age < 60">我是中年人</h1>
<h1 v-else>我是老年人</h1>
1
2
3
4

经过编译后的渲染函数(render):

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_ctx.age < 18)
    ? (_openBlock(), _createBlock("h1", { key: 0 }, "我是小朋友"))
    : (_ctx.age > 18 && _ctx.age < 60)
      ? (_openBlock(), _createBlock("h1", { key: 1 }, "我是中年人"))
      : (_openBlock(), _createBlock("h1", { key: 2 }, "我是老年人"))
}
1
2
3
4
5
6
7

# v-for 指令

如同编程语言中的 for 循环,用来循环创建一个模板,遍历对象可以是数组或者对象。

<!-- for 指令,循环创建 -->
<p v-for="name in welcomes">{{ name }}</p>
<!-- in 和 of 等价 -->
<p v-for="name of welcomes">{{ name }}</p>
<p v-for="(name, index) in welcomes">{{ name }}</p>
<p v-for="(name, index) in welcomes" :key="name.id">{{ name }}</p>
<!-- 遍历对象 -->
<p v-for="(value, name) in myObject">{{ name }}: {{ value }}</p>
1
2
3
4
5
6
7
8

经过编译后的渲染函数(render):

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.welcomes, (name) => {
      return (_openBlock(), _createBlock("p", null, _toDisplayString(name), 1 /* TEXT */))
    }), 256 /* UNKEYED_FRAGMENT */)),
    
    (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.myObject, (value, name) => {
      return (_openBlock(), _createBlock("p", null, _toDisplayString(name) + ": " + _toDisplayString(value), 1 /* TEXT */))
    }), 256 /* UNKEYED_FRAGMENT */))
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7
8
9
10
11

# class 绑定

可以给 HTML 元素绑定一个 class,也可以根据数据内容动态绑定一个 class。

<!-- class -->
<!-- 动态绑定 class,当 isActive 为真时才会使用选择器 active -->
<div :class="{ active: isActive }">class1</div>
<!-- 可以绑定多个 -->
<div :class="{ active: isActive, selected: isSelect}">class2</div>
<!-- 可直接通过一个对象来告诉 vue 该用哪个属性,也就是把模板里的对象换个地方写-->
<div :class="clsObj">class3</div>
<!-- 可使用多个对象,类似于数组 -->
<div :class="[clsObj, clsObj2]">class4</div>
<!-- 直接使用三元表达式 -->
<div :class="[isActive ? activeClass : '']">class5</div>
<!-- 还可以这样用 -->
<div :class="[{ active: isActive }, errorClass]">class6</div>
<!-- class 和  :class 可同时存在 -->
<div class="one-line" :class="{ active: isActive }">class7</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

经过编译后的渲染函数(render),通过渲染函数,你可以看到给元素设置 css 的本质:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("div", {
      class: { active: _ctx.isActive }
    }, "class1", 2 /* CLASS */),

    _createVNode("div", {
      class: { active: _ctx.isActive, selected: _ctx.isSelect}
    }, "class2", 2 /* CLASS */),

    _createVNode("div", { class: _ctx.clsObj }, "class3", 2 /* CLASS */),

    _createVNode("div", {
      class: [_ctx.clsObj, _ctx.clsObj2]
    }, "class4", 2 /* CLASS */),

    _createVNode("div", {
      class: [_ctx.isActive ? _ctx.activeClass : '']
    }, "class5", 2 /* CLASS */),

    _createVNode("div", {
      class: [{ active: _ctx.isActive }, _ctx.errorClass]
    }, "class6", 2 /* CLASS */),

    _createVNode("div", {
      class: ["one-line", { active: _ctx.isActive }]
    }, "class7", 2 /* CLASS */)
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# style 绑定

style 绑定和 class 绑定语法规则基本一样,它的作用是给元素通过 style 添加 css 样式。

<!-- style -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">style1</div>
<!-- 直接使用对象 -->
<div :style="styleObject">style2</div>
<!-- 使用多个对象 -->
<div :style="[colorObj, edgeObj]">style3</div>
<!-- 支持多个值 -->
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">style4</div>
1
2
3
4
5
6
7
8

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createCommentVNode(" style "),
    _createVNode("div", {
      style: { color: _ctx.activeColor, fontSize: _ctx.fontSize + 'px' }
    }, "style1", 4 /* STYLE */),

    _createVNode("div", { style: _ctx.styleObject }, "style2", 4 /* STYLE */),

    _createVNode("div", {
      style: [_ctx.colorObj, _ctx.edgeObj]
    }, "style3", 4 /* STYLE */),

    _createVNode("div", { style: { display: ['-webkit-box', '-ms-flexbox', 'flex'] } }, "style4")
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 事件

可通过 v-on 进行事件绑定,基本规则为 v-on:eventName,eventName 为 DOM 元素所支持的事件。v-on 可以简写换成 @

<!-- 监听事件 -->
<button v-on:click="clickMe">von</button>
<!-- 简写 -->
<button @click="clickMe">von</button>
<!-- 支持动态属性名, eventName 可动态变化,它的值可以是 click、focus-->
<button v-on:[eventName]="eventChange">von</button>
<!-- 指令的特殊修饰符,表示指令以某种特定的方式触发 -->
<form v-on:submit.prevent="onSubmit">提交</form>
1
2
3
4
5
6
7
8

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("button", { onClick: _ctx.clickMe }, "von", 8 /* PROPS */, ["onClick"]),

    _createVNode("button", { onClick: _ctx.clickMe }, "von", 8 /* PROPS */, ["onClick"]),

    _createVNode("button", { [_toHandlerKey(_ctx.eventName)]: _ctx.eventChange }, "von", 16 /* FULL_PROPS */),

    _createVNode("form", {
      onSubmit: _withModifiers(_ctx.onSubmit, ["prevent"])
    }, "提交", 40 /* PROPS, HYDRATE_EVENTS */, ["onSubmit"])
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 绑定属性

无论是自定义组件还是 HTML标签都可以通过 v-bind 把 DOM元素的属性和数据属性进行绑定。

<!-- 通过 v-bind 来绑定属性 -->
<a v-bind:href="url">点我</a>
<!-- 可简写 -->
<a :href="url">点我</a>
1
2
3
4

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("a", { href: _ctx.url }, "点我", 8 /* PROPS */, ["href"]),

    _createVNode("a", { href: _ctx.url }, "点我", 8 /* PROPS */, ["href"])
  ], 64 /* STABLE_FRAGMENT */))
}
1
2
3
4
5
6
7

# v-show 指令

控制元素的显示隐藏,其原理是通过控制 css 属性 display。它属于自定义指令的范畴,你可以使用 Vue 提供的机制进行自定义指令。

<h1 v-show="isShow">Hello!</h1>
1

经过编译后的渲染函数(render),发现这个渲染函数与上面的都不一样,它使用了 _withDirectives,这个是与指令相关的函数。

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _withDirectives((_openBlock(), _createBlock("h1", null, "Hello!", 512 /* NEED_PATCH */)), [
    [_vShow, _ctx.isShow]
  ])
}
1
2
3
4
5

# v-model 双向绑定

与 v-show 指令一样属于自定义指令的范畴,最常用的场景是使用表单的时候,把用户输入的内容和数据进行双向绑定。

<input v-model="question" />
1

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _withDirectives((_openBlock(), _createBlock("input", {
    "onUpdate:modelValue": $event => (_ctx.question = $event)
  }, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
    [_vModelText, _ctx.question]
  ])
}
1
2
3
4
5
6
7

# v-html 指令

直接渲染 HTML 字符串,HTML 不被看做是文本,它会当做 HTML 渲染到页面上。

<div v-html="html">v-html</div>
1

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", { innerHTML: _ctx.html }, null, 8 /* PROPS */, ["innerHTML"]))
}
1
2
3

# v-once 指令

只修改一次,即使数据以后更新了,页面也不会自动更新。

<div v-once>{{ from }}</div>
1

经过编译后的渲染函数(render)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return _cache[1] || (
    _setBlockTracking(-1),
    _cache[1] = _createVNode("div", null, _toDisplayString(_ctx.from), 1 /* TEXT */),
    _setBlockTracking(1),
    _cache[1]
  )
}
1
2
3
4
5
6
7
8

# 最后

本节内容我们重点学习了各个模板的语法规则,以及各个指令的作用。在介绍模板时,也给大家演示了最终生成的渲染函数,有助于大家理解模板的本质。

模板的本质其实就是渲染函数; 渲染函数的本质其实就是操作虚拟DOM; 虚拟DOM的本质其实就是操作真正的 DOM; 最终达到通过 JS 操作真正的 DOM,以让页面动态更新;