Appearance
Vue.js 模板语法
Vue.js 提供了一套基于HTML的模板语法,允许我们声明式地将渲染后的数据绑定到DOM中。
插值
文本插值
使用 双大括号语法进行文本插值:
vue
<template>
<div>
<!-- 基本文本插值 -->
<p>消息: {{ message }}</p>
<!-- JavaScript 表达式 -->
<p>{{ number + 1 }}</p>
<p>{{ ok ? 'YES' : 'NO' }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<!-- 带有修饰符的插值 -->
<p>{{ { a: 1, b: 2 } }}</p> <!-- 会转换为字符串 -->
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello Vue!',
number: 10,
ok: true
}
}
}
</script>
原始 HTML
使用 v-html 指令输出原始HTML内容:
vue
<template>
<div>
<!-- 普通插值,HTML会被转义 -->
<p>{{ rawHtml }}</p>
<!-- v-html 输出原始HTML -->
<div v-html="rawHtml"></div>
<!-- 使用插槽作为更安全的替代方案 -->
<div><slot name="content"></slot></div>
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">这是红色文本</span>'
}
}
}
</script>
特性绑定
使用 v-bind 指令绑定HTML特性:
vue
<template>
<div>
<!-- 动态特性绑定 -->
<img v-bind:src="imageSrc" v-bind:alt="imageAlt">
<!-- 简写形式 -->
<img :src="imageSrc" :alt="imageAlt">
<!-- 动态特性名 -->
<button v-bind:[dynamicAttribute]="attributeValue">动态特性</button>
<!-- 绑定对象 -->
<div v-bind="{ id: staticId, class: className, 'data-value': dataValue }">
绑定对象示例
</div>
<!-- 绑定对象(简写) -->
<div v-bind="objectOfAttrs">
对象属性绑定
</div>
</div>
</template>
<script>
export default {
data() {
return {
imageSrc: '/path/to/image.jpg',
imageAlt: '图片描述',
dynamicAttribute: 'title',
attributeValue: '动态标题',
staticId: 'my-id',
className: 'my-class',
dataValue: 'data-value',
objectOfAttrs: {
id: 'container',
class: 'wrapper',
'data-info': 'info'
}
}
}
}
</script>
指令
v-text
更新元素的文本内容:
vue
<template>
<div>
<!-- 等价于 {{ text }} -->
<span v-text="textContent"></span>
<!-- 会覆盖元素内的任何现有内容 -->
<p v-text="textContent">这段文字会被覆盖</p>
<!-- 普通插值不会覆盖 -->
<p>这段文字不会被覆盖 {{ textContent }}</p>
</div>
</template>
<script>
export default {
data() {
return {
textContent: '这是动态文本内容'
}
}
}
</script>
v-html
更新元素的innerHTML:
vue
<template>
<div>
<!-- 安全的文本插值 -->
<p>{{ rawHtml }}</p>
<!-- 危险的HTML插入,需要确保内容安全 -->
<div v-html="rawHtml"></div>
<!-- 使用Sanitizer API(如果支持) -->
<div ref="htmlContainer"></div>
</div>
</template>
<script>
export default {
data() {
return {
rawHtml: '<p style="color: blue;">这是<em>HTML</em>内容</p>'
}
},
mounted() {
// 使用Sanitizer API(如果浏览器支持)
if (window.Sanitizer) {
this.$refs.htmlContainer.setHTML(this.rawHtml, { sanitizer: new Sanitizer() })
}
}
}
</script>
v-show
条件性地显示或隐藏元素:
vue
<template>
<div>
<!-- 基本用法 -->
<p v-show="showMessage">这是一条消息</p>
<!-- 表达式 -->
<p v-show="counter > 5">计数器大于5时显示</p>
<!-- 复杂条件 -->
<div v-show="isVisible && isActive && !isDisabled">
复杂条件显示
</div>
<!-- 控制按钮 -->
<button @click="toggleMessage">切换消息显示</button>
<button @click="increment">增加计数</button>
</div>
</template>
<script>
export default {
data() {
return {
showMessage: true,
counter: 0,
isVisible: true,
isActive: true,
isDisabled: false
}
},
methods: {
toggleMessage() {
this.showMessage = !this.showMessage
},
increment() {
this.counter++
}
}
}
</script>
v-if, v-else, v-else-if
条件性地渲染元素:
vue
<template>
<div>
<!-- 基本条件渲染 -->
<h1 v-if="type === 'A'">A</h1>
<h1 v-else-if="type === 'B'">B</h1>
<h1 v-else>C</h1>
<!-- 复杂条件 -->
<div v-if="user.loggedIn">
<p>欢迎, {{ user.name }}!</p>
<button @click="logout">登出</button>
</div>
<div v-else>
<p>请先登录</p>
<button @click="login">登录</button>
</div>
<!-- 在组件上使用 -->
<UserComponent v-if="user.loggedIn" :user="user" />
<LoginComponent v-else @login="handleLogin" />
</div>
</template>
<script>
export default {
data() {
return {
type: 'A',
user: {
loggedIn: false,
name: '张三'
}
}
},
methods: {
logout() {
this.user.loggedIn = false
},
login() {
this.user.loggedIn = true
},
handleLogin(userData) {
this.user = { ...this.user, ...userData, loggedIn: true }
}
}
}
</script>
v-for
列表渲染:
vue
<template>
<div>
<!-- 遍历数组 -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }} - {{ item.price }}
</li>
</ul>
<!-- 带索引的遍历 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
<!-- 遍历对象 -->
<ul>
<li v-for="(value, key) in userProfile" :key="key">
{{ key }}: {{ value }}
</li>
</ul>
<!-- 遍历数字 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
<!-- 在组件上使用 -->
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@complete="completeTodo"
/>
<!-- 解构v-for -->
<div v-for="{ id, name, age } in users" :key="id">
<p>{{ name }} - {{ age }}岁</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: '苹果', price: 5 },
{ id: 2, name: '香蕉', price: 3 },
{ id: 3, name: '橙子', price: 4 }
],
userProfile: {
name: '张三',
age: 30,
email: 'zhangsan@example.com'
},
todos: [
{ id: 1, text: '学习Vue', completed: false },
{ id: 2, text: '写代码', completed: true }
],
users: [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 }
]
}
},
methods: {
completeTodo(todoId) {
const todo = this.todos.find(t => t.id === todoId)
if (todo) {
todo.completed = true
}
}
}
}
</script>
v-on (事件处理)
绑定事件监听器:
vue
<template>
<div>
<!-- 基本事件处理 -->
<button v-on:click="counter++">计数器: {{ counter }}</button>
<!-- 简写形式 -->
<button @click="greet">打招呼</button>
<!-- 方法调用 -->
<button @click="say('hi')">说hi</button>
<!-- 内联语句 -->
<button @click="counter += 2">增加2</button>
<!-- 传参 -->
<button @click="addItem('新项目')">添加项目</button>
<!-- 事件修饰符 -->
<!-- .stop - 阻止事件冒泡 -->
<div @click="outerClick">
外层
<button @click.stop="innerClick">内层 - 不会冒泡</button>
</div>
<!-- .prevent - 阻止默认行为 -->
<form @submit.prevent="onSubmit">
<input type="text" v-model="inputValue">
<button type="submit">提交</button>
</form>
<!-- .capture - 使用事件捕获 -->
<div @click.capture="captureClick">
捕获模式
<button @click="normalClick">普通点击</button>
</div>
<!-- .self - 只在事件目标是元素本身时触发 -->
<div @click.self="selfClick">
只在点击自身时触发
<button @click="buttonClick">按钮</button>
</div>
<!-- .once - 事件只触发一次 -->
<button @click.once="onceClick">只触发一次</button>
<!-- .passive - 以被动模式添加事件监听器 -->
<div @scroll.passive="onScroll">滚动区域</div>
<!-- 按键修饰符 -->
<input @keyup.enter="onEnter" placeholder="按Enter键触发">
<input @keyup.esc="onEscape" placeholder="按ESC键触发">
<input @keyup.space="onSpace" placeholder="按空格键触发">
<input @keyup.up="onArrowUp" placeholder="按上箭头键触发">
<!-- 系统修饰键 -->
<div @click.ctrl="onCtrlClick">按住Ctrl点击</div>
<div @click.shift="onShiftClick">按住Shift点击</div>
<div @click.alt="onAltClick">按住Alt点击</div>
<div @click.meta="onMetaClick">按住Meta键(如Cmd)点击</div>
<!-- 鼠标修饰符 -->
<div @click.left="onLeftClick">左键点击</div>
<div @click.right="onRightClick">右键点击</div>
<div @click.middle="onMiddleClick">中键点击</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
inputValue: '',
message: 'Hello'
}
},
methods: {
greet() {
alert(`Hello, ${this.message}!`)
},
say(message) {
alert(message)
},
addItem(item) {
console.log('添加项目:', item)
},
outerClick() {
console.log('外层点击')
},
innerClick() {
console.log('内层点击')
},
onSubmit() {
console.log('表单提交被阻止')
},
captureClick() {
console.log('捕获阶段触发')
},
normalClick() {
console.log('普通点击')
},
selfClick() {
console.log('只在点击自身时触发')
},
buttonClick() {
console.log('按钮点击')
},
onceClick() {
console.log('只触发一次')
},
onScroll() {
console.log('滚动事件')
},
onEnter() {
console.log('按下了Enter键')
},
onEscape() {
console.log('按下了Escape键')
},
onSpace() {
console.log('按下了空格键')
},
onArrowUp() {
console.log('按下了上箭头键')
},
onCtrlClick() {
console.log('按住Ctrl点击')
},
onShiftClick() {
console.log('按住Shift点击')
},
onAltClick() {
console.log('按住Alt点击')
},
onMetaClick() {
console.log('按住Meta键点击')
},
onLeftClick() {
console.log('左键点击')
},
onRightClick() {
console.log('右键点击')
},
onMiddleClick() {
console.log('中键点击')
}
}
}
</script>
v-model (双向数据绑定)
在表单元素上创建双向数据绑定:
vue
<template>
<div>
<!-- 文本输入 -->
<div>
<input v-model="message" placeholder="输入文本">
<p>消息: {{ message }}</p>
</div>
<!-- 多行文本 -->
<div>
<textarea v-model="textareaValue" placeholder="多行文本"></textarea>
<p>多行文本: {{ textareaValue }}</p>
</div>
<!-- 复选框 -->
<div>
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked ? '已选中' : '未选中' }}</label>
</div>
<!-- 多个复选框 -->
<div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>选中: {{ checkedNames }}</span>
</div>
<!-- 单选按钮 -->
<div>
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<br>
<span>选中: {{ picked }}</span>
</div>
<!-- 选择框 -->
<div>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选中: {{ selected }}</span>
</div>
<!-- 多选选择框 -->
<div>
<select v-model="multiSelected" multiple>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>选中: {{ multiSelected }}</span>
</div>
<!-- v-model 修饰符 -->
<!-- .lazy - 在 change 事件而不是 input 事件中更新 -->
<div>
<input v-model.lazy="lazyValue" placeholder="在change事件中更新">
<p>延迟更新值: {{ lazyValue }}</p>
</div>
<!-- .number - 自动将输入值转换为数字 -->
<div>
<input v-model.number="age" type="number" placeholder="输入年龄">
<p>年龄类型: {{ typeof age }}</p>
</div>
<!-- .trim - 自动过滤输入值的首尾空格 -->
<div>
<input v-model.trim="trimmedValue" placeholder="输入带空格的内容">
<p>修剪后值: "{{ trimmedValue }}"</p>
</div>
<!-- 在组件上使用v-model -->
<CustomInput v-model="customValue" />
<p>自定义组件值: {{ customValue }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: '',
textareaValue: '',
checked: false,
checkedNames: [],
picked: '',
selected: '',
multiSelected: [],
lazyValue: '',
age: 0,
trimmedValue: '',
customValue: ''
}
}
}
</script>
动态参数
使用方括号语法使用动态参数:
vue
<template>
<div>
<!-- 动态特性名 -->
<div v-bind:[dynamicAttribute]="attributeValue">
动态特性绑定
</div>
<!-- 动态事件名 -->
<button v-on:[dynamicEvent]="handleDynamicEvent">
动态事件
</button>
<!-- 动态参数的修饰符 -->
<input :[dynamicAttribute].camel="camelValue">
<!-- 按钮用于测试 -->
<button @click="changeDynamicAttribute">改变动态特性</button>
<button @click="changeDynamicEvent">改变动态事件</button>
</div>
</template>
<script>
export default {
data() {
return {
dynamicAttribute: 'title',
attributeValue: '动态标题',
dynamicEvent: 'click',
camelValue: 'camelCase值'
}
},
methods: {
handleDynamicEvent() {
console.log('动态事件被触发')
},
changeDynamicAttribute() {
this.dynamicAttribute = this.dynamicAttribute === 'title' ? 'data-info' : 'title'
this.attributeValue = `新值 ${Date.now()}`
},
changeDynamicEvent() {
this.dynamicEvent = this.dynamicEvent === 'click' ? 'dblclick' : 'click'
}
}
}
</script>
修饰符
事件修饰符
vue
<template>
<div>
<!-- 阻止事件冒泡 -->
<div @click="outerClick" style="padding: 20px; background: lightblue;">
外层
<button @click.stop="innerClick" style="margin: 10px;">内层按钮 - 阻止冒泡</button>
</div>
<!-- 阻止默认行为 -->
<form @submit.prevent="handleSubmit" style="margin: 10px;">
<input type="text" placeholder="表单输入">
<button type="submit">提交(默认行为被阻止)</button>
</form>
<!-- 修饰符串联 -->
<button @click.stop.prevent="bothAction">既阻止冒泡又阻止默认行为</button>
<!-- 只在事件目标是元素本身时触发 -->
<div @click.self="selfOnly" style="padding: 20px; background: lightgreen;">
只在点击此div本身时触发
<p>点击内部元素不会触发</p>
<button>按钮</button>
</div>
<!-- 事件只触发一次 -->
<button @click.once="onceAction">只触发一次</button>
<!-- 捕获模式 -->
<div @click.capture="capturePhase" style="padding: 10px; background: lightcoral;">
捕获阶段
<button @click="bubblePhase">冒泡阶段</button>
</div>
</div>
</template>
<script>
export default {
methods: {
outerClick() {
console.log('外层点击')
},
innerClick() {
console.log('内层点击')
},
handleSubmit() {
console.log('表单提交被阻止')
},
bothAction() {
console.log('阻止冒泡和默认行为')
},
selfOnly() {
console.log('只在点击自身时触发')
},
onceAction() {
console.log('只触发一次')
},
capturePhase() {
console.log('捕获阶段触发')
},
bubblePhase() {
console.log('冒泡阶段触发')
}
}
}
</script>
按键修饰符
vue
<template>
<div>
<!-- 普通按键 -->
<input @keyup.enter="onEnter" placeholder="按Enter键">
<input @keyup.space="onSpace" placeholder="按空格键">
<input @keyup.esc="onEscape" placeholder="按ESC键">
<!-- 方向键 -->
<input @keyup.up="onArrowUp" placeholder="按上箭头">
<input @keyup.down="onArrowDown" placeholder="按下箭头">
<input @keyup.left="onArrowLeft" placeholder="按左箭头">
<input @keyup.right="onArrowRight" placeholder="按右箭头">
<!-- 系统修饰键 -->
<input @keyup.ctrl="onCtrl" placeholder="按Ctrl键">
<input @keyup.alt="onAlt" placeholder="按Alt键">
<input @keyup.shift="onShift" placeholder="按Shift键">
<input @keyup.meta="onMeta" placeholder="按Meta键(Cmd)">
<!-- 组合键 -->
<input @keyup.ctrl.a="onCtrlA" placeholder="按Ctrl+A">
<input @keyup.ctrl."="="onCtrlPlus" placeholder="按Ctrl+Plus">
<!-- 系统修饰键组合 -->
<div @keyup.ctrl.exact="onCtrlOnly">只按Ctrl键时触发</div>
<div @keyup.exact="onNoneModifier">没有任何系统修饰键时触发</div>
<!-- 鼠标修饰符 -->
<div @click.left="onLeftClick">左键点击</div>
<div @click.right="onRightClick">右键点击</div>
<div @click.middle="onMiddleClick">中键点击</div>
</div>
</template>
<script>
export default {
methods: {
onEnter() { console.log('Enter键') },
onSpace() { console.log('空格键') },
onEscape() { console.log('ESC键') },
onArrowUp() { console.log('上箭头') },
onArrowDown() { console.log('下箭头') },
onArrowLeft() { console.log('左箭头') },
onArrowRight() { console.log('右箭头') },
onCtrl() { console.log('Ctrl键') },
onAlt() { console.log('Alt键') },
onShift() { console.log('Shift键') },
onMeta() { console.log('Meta键') },
onCtrlA() { console.log('Ctrl+A') },
onCtrlPlus() { console.log('Ctrl+Plus') },
onCtrlOnly() { console.log('只按Ctrl键') },
onNoneModifier() { console.log('没有任何修饰键') },
onLeftClick() { console.log('左键点击') },
onRightClick() { console.log('右键点击') },
onMiddleClick() { console.log('中键点击') }
}
}
</script>
组件上的 v-model
vue
<!-- CustomInput.vue -->
<template>
<div class="custom-input">
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
:class="{ error: hasError }"
v-bind="$attrs"
>
<span v-if="hasError" class="error-message">{{ errorMessage }}</span>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
modelValue: String,
validator: Function
},
emits: ['update:modelValue'],
computed: {
hasError() {
return this.validator && !this.validator(this.modelValue)
},
errorMessage() {
return this.hasError ? '输入不合法' : ''
}
}
}
</script>
<!-- 父组件使用 -->
<template>
<div>
<h3>组件上的 v-model</h3>
<CustomInput
v-model="inputValue"
:validator="validateInput"
placeholder="请输入至少3个字符"
/>
<p>输入值: {{ inputValue }}</p>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue'
export default {
components: {
CustomInput
},
data() {
return {
inputValue: ''
}
},
methods: {
validateInput(value) {
return value && value.length >= 3
}
}
}
</script>
v-slot (插槽)
默认插槽和具名插槽
vue
<!-- Card.vue -->
<template>
<div class="card">
<div class="card-header">
<slot name="header">默认头部</slot>
</div>
<div class="card-body">
<slot>默认内容</slot>
</div>
<div class="card-footer">
<slot name="footer">默认底部</slot>
</div>
</div>
</template>
<script>
export default {
name: 'Card'
}
</script>
<style scoped>
.card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.card-header, .card-footer {
background-color: #f8f9fa;
padding: 12px 16px;
}
.card-body {
padding: 16px;
}
</style>
vue
<!-- 使用插槽 -->
<template>
<div>
<Card>
<template #header>
<h3>自定义头部</h3>
</template>
<p>这是自定义内容</p>
<template #footer>
<button>操作按钮</button>
</template>
</Card>
</div>
</template>
<script>
import Card from './Card.vue'
export default {
components: {
Card
}
}
</script>
作用域插槽
vue
<!-- UserList.vue -->
<template>
<div class="user-list">
<div
v-for="(user, index) in users"
:key="user.id"
class="user-item"
>
<slot
:user="user"
:index="index"
:isEven="index % 2 === 0"
:isActive="user.active"
>
<!-- 默认插槽内容 -->
<span>{{ user.name }}</span>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'UserList',
props: {
users: {
type: Array,
default: () => []
}
}
}
</script>
vue
<!-- 使用作用域插槽 -->
<template>
<div>
<UserList :users="users">
<template #default="{ user, index, isEven, isActive }">
<div :class="['user-item', { even: isEven, active: isActive }]">
<strong>{{ user.name }}</strong> - {{ user.email }}
<span class="index">(#{{ index + 1 }})</span>
<span v-if="isActive" class="status">活跃</span>
</div>
</template>
</UserList>
</div>
</template>
<script>
import UserList from './UserList.vue'
export default {
components: {
UserList
},
data() {
return {
users: [
{ id: 1, name: '张三', email: 'zhangsan@example.com', active: true },
{ id: 2, name: '李四', email: 'lisi@example.com', active: false },
{ id: 3, name: '王五', email: 'wangwu@example.com', active: true }
]
}
}
}
</script>
Vue.js 的模板语法提供了一套简洁而强大的声明式渲染机制,通过这些语法可以轻松地将数据渲染到DOM中。