深入理解Vue3模板语法原理,掌握各种数据绑定方式
Vue3的模板语法基于HTML,通过特殊的语法将数据绑定到DOM。模板会被Vue的编译器编译成渲染函数,最终生成虚拟DOM。
编译过程:HTML模板 → 抽象语法树(AST) → 渲染函数 → 虚拟DOM → 真实DOM
使用双大括号{{ }}进行文本插值,这是Vue最基本的数据绑定方式。
<template>
<div>
<!-- 简单文本插值 -->
<p>{{ message }}</p>
<!-- JavaScript表达式 -->
<p>{{ firstName + ' ' + lastName }}</p>
<p>{{ count * 2 }}</p>
<p>{{ isActive ? '激活' : '未激活' }}</p>
<!-- 方法调用 -->
<p>{{ getFullName() }}</p>
<!-- 计算属性 -->
<p>{{ reversedMessage }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue3',
firstName: '张',
lastName: '三',
count: 10,
isActive: true
}
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
},
methods: {
getFullName() {
return this.firstName + this.lastName
}
}
}
</script>
使用v-html指令可以渲染原始HTML内容,而不是将其作为纯文本显示。
<template>
<div>
<!-- 文本插值:显示原始HTML代码 -->
<p>普通文本: {{ rawHtml }}</p>
<!-- v-html指令:渲染HTML内容 -->
<p>HTML内容: <span v-html="rawHtml"></span></p>
<!-- 动态HTML内容 -->
<div v-html="dynamicContent"></div>
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<strong style="color: red;">加粗的红色文本</strong>',
dynamicContent: '<div class="alert alert-success">操作成功!</div>'
}
}
}
</script>
XSS攻击风险:v-html会直接插入HTML到DOM中,可能导致跨站脚本攻击。
<template>
<div>
<!-- 不安全:用户输入 -->
<div v-html="userInput"></div>
<!-- 安全:可信内容 -->
<div v-html="trustedContent"></div>
<!-- 更安全:使用组件 -->
<RichTextRenderer :content="sanitizedContent" />
</div>
</template>
<script>
import DOMPurify from 'dompurify'
export default {
data() {
return {
userInput: '', // 危险!不要直接渲染
trustedContent: '<p>来自可信来源的内容</p>'
}
},
computed: {
sanitizedContent() {
return DOMPurify.sanitize(this.userInput)
}
}
}
</script>
v-bind指令用于动态绑定HTML属性,将属性值与Vue实例的数据关联起来。
响应式绑定机制:当绑定的数据发生变化时,Vue会自动更新对应的DOM属性。
// 模板中的v-bind
<img :src="imageUrl" :alt="imageAlt">
// 编译后的渲染函数
h('img', {
src: _ctx.imageUrl,
alt: _ctx.imageAlt
})
<template>
<div>
<!-- 1. 完整语法 -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">
<!-- 2. 简写语法(推荐) -->
<img :src="imageUrl" :alt="imageAlt">
<!-- 3. 动态属性名 -->
<button :[attributeName]="value">按钮</button>
<!-- 4. 布尔属性 -->
<button :disabled="isDisabled">提交</button>
<!-- 5. 绑定对象 -->
<div v-bind="objectOfAttrs">绑定多个属性</div>
<!-- 6. 表达式绑定 -->
<a :href="'/user/' + userId">用户详情</a>
<!-- 7. 方法返回值绑定 -->
<input :value="getInputValue()">
</div>
</template>
<script>
export default {
data() {
return {
imageUrl: 'https://vuejs.org/logo.svg',
imageAlt: 'Vue Logo',
attributeName: 'id',
value: 'dynamic-id',
isDisabled: true,
userId: 123,
objectOfAttrs: {
id: 'container',
class: 'wrapper',
'data-test': 'test-value'
}
}
},
methods: {
getInputValue() {
return this.userId.toString()
}
}
}
</script>
<template>
<div>
<!-- 对象语法 -->
<div :class="{ active: isActive, 'text-danger': hasError }">
对象语法
</div>
<!-- 数组语法 -->
<div :class="[activeClass, errorClass]">
数组语法
</div>
<!-- 数组与对象混合 -->
<div :class="[{ active: isActive }, errorClass]">
混合语法
</div>
</div>
</template>
<script>
export default {
data() {
return {
isActive: true,
hasError: false,
activeClass: 'active',
errorClass: 'text-danger'
}
}
}
</script>
<template>
<div>
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
对象语法
</div>
<!-- 绑定样式对象 -->
<div :style="styleObject">
样式对象
</div>
<!-- 数组语法 -->
<div :style="[baseStyles, overridingStyles]">
数组语法
</div>
</div>
</template>
<script>
export default {
data() {
return {
activeColor: 'red',
fontSize: 30,
styleObject: {
color: 'blue',
fontSize: '20px'
},
baseStyles: { color: 'green' },
overridingStyles: { fontSize: '25px' }
}
}
}
</script>
<template>
<div>
<!-- 文本输入 -->
<input v-model="message" placeholder="输入文本">
<p>输入的内容: {{ message }}</p>
<!-- 多行文本 -->
<textarea v-model="description"></textarea>
<!-- 复选框 -->
<input type="checkbox" v-model="checked">
<p>选中状态: {{ checked }}</p>
<!-- 单选按钮 -->
<input type="radio" value="男" v-model="gender"> 男
<input type="radio" value="女" v-model="gender"> 女
<p>选择: {{ gender }}</p>
<!-- 下拉选择 -->
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
description: '',
checked: false,
gender: '',
selected: ''
}
}
}
</script>
<template>
<div>
<!-- .lazy: 在change事件后同步 -->
<input v-model.lazy="msg">
<!-- .number: 自动转换为数字 -->
<input v-model.number="age" type="number">
<!-- .trim: 自动过滤首尾空白字符 -->
<input v-model.trim="username">
</div>
</template>
<script>
export default {
data() {
return {
msg: '',
age: 0,
username: ''
}
}
}
</script>
<template>
<div>
<p>{{ message }}</p>
<input v-model="message">
<button :disabled="isDisabled">按钮</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue3')
const isDisabled = ref(false)
</script>