模板语法 #

一、模板概述 #

Vue使用基于HTML的模板语法,允许开发者声明式地将DOM绑定到底层组件实例的数据上。

1.1 底层原理 #

text
模板 → 编译 → 渲染函数 → 虚拟DOM → 真实DOM

1.2 两种渲染方式 #

vue
<!-- 模板语法(推荐) -->
<template>
  <div>{{ message }}</div>
</template>

<!-- 渲染函数 -->
<script>
import { h } from 'vue'

export default {
  render() {
    return h('div', this.message)
  }
}
</script>

二、文本插值 #

2.1 基本插值 #

vue
<template>
  <!-- 双大括号语法 -->
  <p>{{ message }}</p>
  
  <!-- 支持表达式 -->
  <p>{{ number + 1 }}</p>
  <p>{{ ok ? 'YES' : 'NO' }}</p>
  <p>{{ message.split('').reverse().join('') }}</p>
  
  <!-- 调用方法 -->
  <p>{{ formatDate(date) }}</p>
</template>

<script setup>
import { ref } from 'vue'

const message = ref('Hello Vue!')
const number = ref(10)
const ok = ref(true)
const date = ref(new Date())

function formatDate(d) {
  return d.toLocaleDateString('zh-CN')
}
</script>

2.2 表达式限制 #

vue
<template>
  <!-- 有效表达式 -->
  <p>{{ count + 1 }}</p>
  <p>{{ Math.random() > 0.5 ? '大' : '小' }}</p>
  
  <!-- 无效表达式(不能使用语句) -->
  <!-- <p>{{ var a = 1 }}</p> -->
  <!-- <p>{{ if (ok) { return message } }}</p> -->
</template>

2.3 访问全局变量 #

javascript
// main.js
const app = createApp({})

// 配置全局变量
app.config.globalProperties.$formatDate = (date) => {
  return new Date(date).toLocaleDateString('zh-CN')
}

// 模板中可访问的全局对象
// Math, Date, Array, Object, JSON, parseInt, parseFloat 等

三、原始HTML #

3.1 v-html指令 #

vue
<template>
  <!-- 普通插值(转义HTML) -->
  <p>{{ rawHtml }}</p>
  <!-- 输出: &lt;span style="color: red"&gt;红色文字&lt;/span&gt; -->
  
  <!-- v-html(渲染HTML) -->
  <p v-html="rawHtml"></p>
  <!-- 输出: 红色文字 -->
</template>

<script setup>
const rawHtml = '<span style="color: red">红色文字</span>'
</script>

3.2 安全警告 #

vue
<template>
  <!-- 危险!不要在用户输入上使用v-html -->
  <!-- <div v-html="userInput"></div> -->
  
  <!-- 安全做法:使用纯文本 -->
  <div>{{ userInput }}</div>
</template>

<script setup>
// 永远不要在用户提交的内容上使用v-html
// 可能导致XSS攻击
const userInput = '' // 用户输入
</script>

四、属性绑定 #

4.1 基本绑定 #

vue
<template>
  <!-- 完整写法 -->
  <div v-bind:id="dynamicId"></div>
  
  <!-- 简写形式 -->
  <div :id="dynamicId"></div>
  
  <!-- 多个属性 -->
  <div :id="dynamicId" :class="dynamicClass" :style="dynamicStyle"></div>
</template>

<script setup>
import { ref } from 'vue'

const dynamicId = ref('my-id')
const dynamicClass = ref('active')
const dynamicStyle = ref({ color: 'red' })
</script>

4.2 动态属性名 #

vue
<template>
  <!-- 动态属性名 -->
  <button :[attrName]="attrValue">按钮</button>
  
  <!-- 动态事件名 -->
  <button @[eventName]="handleClick">点击</button>
</template>

<script setup>
import { ref } from 'vue'

const attrName = ref('disabled')
const attrValue = ref(true)
const eventName = ref('click')

function handleClick() {
  console.log('点击了')
}
</script>

4.3 绑定对象 #

vue
<template>
  <!-- 绑定多个属性 -->
  <div v-bind="objectOfAttrs"></div>
  
  <!-- 等价于 -->
  <div :id="objectOfAttrs.id" :class="objectOfAttrs.class"></div>
</template>

<script setup>
const objectOfAttrs = {
  id: 'container',
  class: 'wrapper active'
}
</script>

4.4 布尔型属性 #

vue
<template>
  <!-- 布尔属性 -->
  <button :disabled="isDisabled">按钮</button>
  
  <!-- isDisabled为false时,disabled属性不会被渲染 -->
  <button :disabled="false">按钮</button>
  
  <!-- 其他布尔属性 -->
  <input type="checkbox" :checked="isChecked">
  <input type="text" :readonly="isReadonly">
  <input type="text" :required="isRequired">
</template>

<script setup>
import { ref } from 'vue'

const isDisabled = ref(true)
const isChecked = ref(true)
const isReadonly = ref(false)
const isRequired = ref(true)
</script>

五、Class与Style绑定 #

5.1 绑定Class #

vue
<template>
  <!-- 对象语法 -->
  <div :class="{ active: isActive, 'text-danger': hasError }"></div>
  
  <!-- 数组语法 -->
  <div :class="[activeClass, errorClass]"></div>
  
  <!-- 数组与对象混用 -->
  <div :class="[activeClass, { 'text-danger': hasError }]"></div>
  
  <!-- 与普通class共存 -->
  <div class="static" :class="{ active: isActive }"></div>
</template>

<script setup>
import { ref } from 'vue'

const isActive = ref(true)
const hasError = ref(false)
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>

5.2 绑定Style #

vue
<template>
  <!-- 对象语法 -->
  <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
  
  <!-- 绑定样式对象 -->
  <div :style="styleObject"></div>
  
  <!-- 数组语法 -->
  <div :style="[baseStyles, overridingStyles]"></div>
  
  <!-- 多重值(浏览器自动选择支持的) -->
  <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div>
</template>

<script setup>
import { ref } from 'vue'

const activeColor = ref('red')
const fontSize = ref(30)

const styleObject = ref({
  color: 'red',
  fontSize: '13px'
})

const baseStyles = ref({
  color: 'blue',
  fontSize: '14px'
})

const overridingStyles = ref({
  color: 'green'
})
</script>

六、条件渲染 #

6.1 v-if / v-else / v-else-if #

vue
<template>
  <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>
    其他类型
  </div>
</template>

<script setup>
import { ref } from 'vue'
const type = ref('A')
</script>

6.2 template上的v-if #

vue
<template>
  <!-- 在template上使用v-if -->
  <template v-if="ok">
    <h1>标题</h1>
    <p>段落1</p>
    <p>段落2</p>
  </template>
</template>

6.3 v-show #

vue
<template>
  <!-- v-show只是切换display属性 -->
  <div v-show="isShow">显示/隐藏</div>
  
  <!-- v-show不支持template -->
  <!-- <template v-show="isShow">...</template> -->
</template>

6.4 v-if vs v-show #

特性 v-if v-show
切换方式 销毁和重建DOM CSS display切换
初始渲染 条件为false时不渲染 始终渲染
性能 切换开销大 初始渲染开销大
适用场景 条件很少改变 需要频繁切换

6.5 key管理 #

vue
<template>
  <!-- 使用key强制重新渲染 -->
  <div v-if="currentType === 'username'" key="username">
    <input type="text" placeholder="用户名">
  </div>
  <div v-else key="email">
    <input type="text" placeholder="邮箱">
  </div>
  
  <!-- 不使用key会复用元素 -->
  <div v-if="currentType === 'username'">
    <input type="text" placeholder="用户名">
  </div>
  <div v-else>
    <input type="text" placeholder="邮箱">
  </div>
</template>

七、列表渲染 #

7.1 遍历数组 #

vue
<template>
  <!-- 基本遍历 -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
  
  <!-- 带索引 -->
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index }}: {{ item.name }}
    </li>
  </ul>
  
  <!-- 遍历数字 -->
  <span v-for="n in 10" :key="n">{{ n }}</span>
</template>

<script setup>
const items = [
  { id: 1, name: '苹果' },
  { id: 2, name: '香蕉' },
  { id: 3, name: '橙子' }
]
</script>

7.2 遍历对象 #

vue
<template>
  <!-- 遍历值 -->
  <div v-for="value in object" :key="value">
    {{ value }}
  </div>
  
  <!-- 遍历键和值 -->
  <div v-for="(value, key) in object" :key="key">
    {{ key }}: {{ value }}
  </div>
  
  <!-- 遍历键、值和索引 -->
  <div v-for="(value, key, index) in object" :key="key">
    {{ index }}. {{ key }}: {{ value }}
  </div>
</template>

<script setup>
const object = {
  name: '张三',
  age: 25,
  city: '北京'
}
</script>

7.3 template上的v-for #

vue
<template>
  <ul>
    <template v-for="item in items" :key="item.id">
      <li>{{ item.name }}</li>
      <li class="divider"></li>
    </template>
  </ul>
</template>

7.4 key的重要性 #

vue
<template>
  <!-- 正确:使用唯一id -->
  <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  
  <!-- 不推荐:使用索引(可能导致问题) -->
  <li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
  
  <!-- 错误:没有key -->
  <!-- <li v-for="item in items">{{ item.name }}</li> -->
</template>

八、事件处理 #

8.1 内联事件处理器 #

vue
<template>
  <!-- 内联语句 -->
  <button @click="count++">{{ count }}</button>
  
  <!-- 调用方法 -->
  <button @click="say('hello')">Say hello</button>
  
  <!-- 访问事件对象 -->
  <button @click="warn('警告', $event)">警告</button>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)

function say(message) {
  alert(message)
}

function warn(message, event) {
  if (event) {
    event.preventDefault()
  }
  alert(message)
}
</script>

8.2 方法事件处理器 #

vue
<template>
  <button @click="handleClick">点击</button>
</template>

<script setup>
function handleClick(event) {
  // event是原生DOM事件
  console.log(event.target)
}
</script>

8.3 事件修饰符 #

vue
<template>
  <!-- 阻止默认行为 -->
  <form @submit.prevent="onSubmit">
    <button type="submit">提交</button>
  </form>
  
  <!-- 阻止事件冒泡 -->
  <div @click="onDivClick">
    <button @click.stop="onBtnClick">按钮</button>
  </div>
  
  <!-- 事件捕获模式 -->
  <div @click.capture="doThis">...</div>
  
  <!-- 只当事件在该元素本身触发 -->
  <div @click.self="doThat">...</div>
  
  <!-- 事件只触发一次 -->
  <button @click.once="doOnce">只触发一次</button>
  
  <!-- 链式调用 -->
  <div @click.stop.prevent="doThat">...</div>
</template>

8.4 按键修饰符 #

vue
<template>
  <!-- 按键别名 -->
  <input @keyup.enter="submit">
  <input @keyup.tab="nextInput">
  <input @keyup.esc="cancel">
  <input @keyup.space="addSpace">
  <input @keyup.up="moveUp">
  <input @keyup.down="moveDown">
  <input @keyup.left="moveLeft">
  <input @keyup.right="moveRight">
  
  <!-- 系统修饰键 -->
  <input @keyup.ctrl="onCtrl">
  <input @keyup.alt="onAlt">
  <input @keyup.shift="onShift">
  <input @keyup.meta="onMeta">
  
  <!-- 组合修饰键 -->
  <input @keyup.ctrl.enter="onCtrlEnter">
  <input @click.ctrl="onClickCtrl">
  
  <!-- .exact精确修饰符 -->
  <button @click.ctrl.exact="onCtrlOnly">只有Ctrl</button>
  <button @click.ctrl.shift.exact="onCtrlShift">Ctrl+Shift</button>
</template>

8.5 鼠标按钮修饰符 #

vue
<template>
  <button @click.left="onLeftClick">左键</button>
  <button @click.middle="onMiddleClick">中键</button>
  <button @click.right="onRightClick">右键</button>
</template>

九、v-model表单绑定 #

9.1 文本输入 #

vue
<template>
  <input v-model="text" placeholder="输入文本">
  <p>输入内容: {{ text }}</p>
  
  <textarea v-model="content" placeholder="多行文本"></textarea>
  <p>内容: {{ content }}</p>
</template>

<script setup>
import { ref } from 'vue'

const text = ref('')
const content = ref('')
</script>

9.2 复选框 #

vue
<template>
  <!-- 单个复选框 -->
  <input type="checkbox" v-model="checked">
  <label>{{ checked }}</label>
  
  <!-- 多个复选框 -->
  <input type="checkbox" v-model="checkedNames" value="张三">
  <label>张三</label>
  <input type="checkbox" v-model="checkedNames" value="李四">
  <label>李四</label>
  <input type="checkbox" v-model="checkedNames" value="王五">
  <label>王五</label>
  <p>选中: {{ checkedNames }}</p>
</template>

<script setup>
import { ref } from 'vue'

const checked = ref(false)
const checkedNames = ref([])
</script>

9.3 单选按钮 #

vue
<template>
  <input type="radio" v-model="picked" value="one">
  <label>选项一</label>
  <input type="radio" v-model="picked" value="two">
  <label>选项二</label>
  <p>选中: {{ picked }}</p>
</template>

<script setup>
import { ref } from 'vue'
const picked = ref('one')
</script>

9.4 选择框 #

vue
<template>
  <!-- 单选 -->
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option value="a">A</option>
    <option value="b">B</option>
    <option value="c">C</option>
  </select>
  
  <!-- 多选 -->
  <select v-model="selectedArr" multiple>
    <option value="a">A</option>
    <option value="b">B</option>
    <option value="c">C</option>
  </select>
  
  <!-- 动态选项 -->
  <select v-model="selectedId">
    <option v-for="option in options" :key="option.id" :value="option.id">
      {{ option.name }}
    </option>
  </select>
</template>

<script setup>
import { ref } from 'vue'

const selected = ref('')
const selectedArr = ref([])
const selectedId = ref(1)
const options = [
  { id: 1, name: '选项一' },
  { id: 2, name: '选项二' },
  { id: 3, name: '选项三' }
]
</script>

9.5 值绑定 #

vue
<template>
  <!-- 复选框 -->
  <input 
    type="checkbox" 
    v-model="toggle"
    :true-value="trueValue"
    :false-value="falseValue"
  >
  <p>{{ toggle }}</p>
  
  <!-- 单选按钮 -->
  <input type="radio" v-model="pick" :value="firstValue">
  <input type="radio" v-model="pick" :value="secondValue">
</template>

<script setup>
import { ref } from 'vue'

const toggle = ref('yes')
const trueValue = 'yes'
const falseValue = 'no'
const pick = ref('')
const firstValue = 'first'
const secondValue = 'second'
</script>

9.6 修饰符 #

vue
<template>
  <!-- .lazy - 在change事件后同步 -->
  <input v-model.lazy="text">
  
  <!-- .number - 转换为数字 -->
  <input v-model.number="age" type="number">
  
  <!-- .trim - 去除首尾空格 -->
  <input v-model.trim="name">
</template>

十、总结 #

语法 说明
{{ }} 文本插值
v-html 输出原始HTML
v-bind/: 属性绑定
v-on/@ 事件绑定
v-model 双向绑定
v-if 条件渲染(销毁/重建)
v-show 条件渲染(CSS切换)
v-for 列表渲染
v-slot/# 插槽

模板语法要点:

  • 使用双大括号进行文本插值
  • v-bind用于属性绑定,可简写为:
  • v-on用于事件绑定,可简写为@
  • v-model实现表单双向绑定
  • v-if和v-show有不同的使用场景
  • v-for需要配合key使用
最后更新:2026-03-26