Vue3 模板语法详解 - 初学者完全指南

Vue3 模板语法详解 - 初学者完全指南

注意: 复制文中示例代码到 play.vuejs.org/ 即可直接看到结果。

什么是模板语法?

Vue 模板语法就是在 HTML 中嵌入 Vue 的特殊指令和表达式,让 HTML 能够响应数据变化

想象一下:

  • 普通 HTML:静态的,内容不会变
  • Vue 模板:动态的,内容会根据数据自动更新

模板语法的核心概念

csharp 复制代码
Vue 模板语法分类:
┌─────────────────────────────────────┐
│          数据绑定语法               │
│  ┌───────────────────────────────┐  │
│  │ {{ }} 插值                    │  │
│  │ v-bind 属性绑定               │  │
│  └───────────────────────────────┘  │
├─────────────────────────────────────┤
│          指令语法                  │
│  ┌───────────────────────────────┐  │
│  │ v-if 条件渲染                 │  │
│  │ v-for 列表渲染                │  │
│  │ v-on 事件处理                 │  │
│  │ v-model 双向绑定              │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

1. 数据绑定语法

1.1 文本插值 {{ }}

vue 复制代码
<template>
  <div>
    <h1>欢迎来到 {{ appName }}!</h1>
    <p>当前时间:{{ currentTime }}</p>
    <p>计算结果:{{ count * 2 + 10 }}</p>
    <p>消息:{{ message.toUpperCase() }}</p>
  </div>
</template>

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

const appName = '我的Vue应用'
const currentTime = new Date().toLocaleString()
const count = ref(5)
const message = ref('hello world')
</script>

1.2 属性绑定 v-bind

vue 复制代码
<template>
  <div>
    <!-- 完整语法 -->
    <img v-bind:src="imageUrl" v-bind:alt="imageAlt">
    <a v-bind:href="linkUrl" v-bind:title="linkTitle">链接</a>
    
    <!-- 简写语法 (常用) -->
    <img :src="imageUrl" :alt="imageAlt">
    <a :href="linkUrl" :title="linkTitle">链接</a>
    
    <!-- 动态绑定 class -->
    <div :class="containerClass">容器</div>
    <div :class="{ active: isActive, disabled: isDisabled }">动态类</div>
    <div :class="[baseClass, isActive ? 'active' : '']">数组类</div>
    
    <!-- 动态绑定 style -->
    <div :style="{ color: textColor, fontSize: fontSize + 'px' }">样式绑定</div>
    <div :style="computedStyle">对象样式</div>
  </div>
</template>

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

const imageUrl = 'https://picsum.photos/200/300'
const imageAlt = '随机图片'
const linkUrl = 'https://vuejs.org'
const linkTitle = 'Vue 官网'

const isActive = ref(true)
const isDisabled = ref(false)
const containerClass = ref('container')

const baseClass = 'base'
const textColor = ref('red')
const fontSize = ref(18)

const computedStyle = computed(() => ({
  color: textColor.value,
  fontSize: fontSize.value + 'px',
  fontWeight: 'bold'
}))
</script>

2. 条件渲染指令

2.1 v-ifv-else-ifv-else

vue 复制代码
<template>
  <div>
    <!-- 基础条件渲染 -->
    <h1 v-if="isLoggedIn">欢迎回来,{{ userName }}!</h1>
    <h1 v-else>请先登录</h1>
    
    <!-- 多条件分支 -->
    <div v-if="userRole === 'admin'">
      <h2>管理员面板</h2>
      <button>管理用户</button>
      <button>系统设置</button>
    </div>
    <div v-else-if="userRole === 'user'">
      <h2>用户面板</h2>
      <button>查看资料</button>
      <button>修改密码</button>
    </div>
    <div v-else>
      <h2>访客模式</h2>
      <button @click="showLogin">登录</button>
    </div>
    
    <!-- 使用 template 包装多个元素 -->
    <template v-if="showDetails">
      <h3>详细信息</h3>
      <p>邮箱:{{ userEmail }}</p>
      <p>注册时间:{{ registerTime }}</p>
    </template>
  </div>
</template>

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

const isLoggedIn = ref(false)
const userName = ref('张三')
const userRole = ref('user')
const showDetails = ref(true)
const userEmail = ref('zhangsan@example.com')
const registerTime = ref('2024-01-01')

const showLogin = () => {
  isLoggedIn.value = true
}
</script>

2.2 v-show

vue 复制代码
<template>
  <div>
    <!-- v-show 只是切换 display 样式 -->
    <div v-show="isVisible">这个元素会显示/隐藏</div>
    <button @click="toggleVisibility">切换显示</button>
    
    <!-- v-show vs v-if 的区别 -->
    <div v-if="condition1">v-if:条件为假时元素不存在</div>
    <div v-show="condition2">v-show:条件为假时元素仍存在,只是隐藏</div>
  </div>
</template>

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

const isVisible = ref(true)
const condition1 = ref(false)
const condition2 = ref(false)

const toggleVisibility = () => {
  isVisible.value = !isVisible.value
}
</script>

3. 列表渲染指令 v-for

3.1 基础列表渲染

vue 复制代码
<template>
  <div>
    <!-- 遍历数组 -->
    <ul>
      <li v-for="item in items" :key="item.id">
        {{ item.name }} - {{ item.price }}
      </li>
    </ul>
    
    <!-- 遍历时获取索引 ------此处的 item 和 index 不能颠倒 -->
    <div v-for="(item, index) in items" :key="item.id">
      <p>第 {{ index + 1 }} 项:{{ item.name }}</p>
    </div>
    
    <!-- 遍历对象 ------ index 要在最后一个 -->
    <div v-for="(value, key, index) in userInfo" :key="key">
      <p>{{ index + 1 }}. {{ key }}: {{ value }}</p>
    </div>
    
    <!-- 遍历数字 -->
    <div v-for="n in 5" :key="n">
      第 {{ n }} 项
    </div>
  </div>
</template>

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

const items = ref([
  { id: 1, name: '苹果', price: 5 },
  { id: 2, name: '香蕉', price: 3 },
  { id: 3, name: '橙子', price: 4 }
])

const userInfo = ref({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com',
  city: '北京'
})
</script>

3.2 嵌套循环

vue 复制代码
<template>
  <div>
    <!-- 嵌套循环渲染分类商品 -->
    <div v-for="category in categories" :key="category.id">
      <h2>{{ category.name }}</h2>
      <ul>
        <li v-for="product in category.products" :key="product.id">
          {{ product.name }} - ¥{{ product.price }}
        </li>
      </ul>
    </div>
  </div>
</template>

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

const categories = ref([
  {
    id: 1,
    name: '水果',
    products: [
      { id: 1, name: '苹果', price: 5 },
      { id: 2, name: '香蕉', price: 3 }
    ]
  },
  {
    id: 2,
    name: '蔬菜',
    products: [
      { id: 3, name: '白菜', price: 2 },
      { id: 4, name: '萝卜', price: 1.5 }
    ]
  }
])
</script>

4. 事件处理指令 v-on

4.1 基础事件绑定

vue 复制代码
<template>
  <div>
    <!-- 基础事件 -->
    <button v-on:click="handleClick">点击我</button>
    <button @click="handleClick">简写语法</button>
    
    <!-- 表单事件 -->
    <input @input="handleInput" placeholder="输入内容">
    <input @change="handleChange" placeholder="改变后触发">
    <input @focus="handleFocus" @blur="handleBlur" placeholder="焦点事件">
    
    <!-- 键盘事件 -->
    <input @keyup.enter="handleEnter" placeholder="按回车触发">
    <input @keyup.delete="handleDelete" placeholder="按删除键触发">
    
    <!-- 鼠标事件 -->
    <div 
      @mouseenter="handleMouseEnter" 
      @mouseleave="handleMouseLeave"
      style="padding: 20px; background: #f0f0f0; margin: 10px 0;"
    >
      鼠标悬停区域
    </div>
    
    <!-- 事件修饰符 -->
    <button @click.stop="handleClick">阻止事件冒泡</button>
    <button @click.prevent="handleClick">阻止默认行为</button>
    <button @click.once="handleClick">只触发一次</button>
  </div>
</template>

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

const message = ref('')

const handleClick = () => {
  alert('按钮被点击了!')
}

const handleInput = (event) => {
  message.value = event.target.value
  console.log('输入内容:', message.value)
}

const handleChange = (event) => {
  console.log('内容改变:', event.target.value)
}

const handleFocus = () => {
  console.log('获得焦点')
}

const handleBlur = () => {
  console.log('失去焦点')
}

const handleEnter = (event) => {
  console.log('按回车:', event.target.value)
}

const handleDelete = () => {
  console.log('按删除键')
}

const handleMouseEnter = () => {
  console.log('鼠标进入')
}

const handleMouseLeave = () => {
  console.log('鼠标离开')
}
</script>

4.2 事件传参

vue 复制代码
<template>
  <div>
    <!-- 传递静态参数 -->
    <button @click="greet('张三')">问候张三</button>
    <button @click="greet('李四')">问候李四</button>
    
    <!-- 传递动态参数 -->
    <button 
      v-for="user in users" 
      :key="user.id"
      @click="selectUser(user)"
    >
      选择 {{ user.name }}
    </button>
    
    <!-- 传递事件对象 -->
    <input @click="handleClickWithEvent" value="点击我">
    
    <!-- 内联处理器 -->
    <button @click="count++">计数:{{ count }}</button>
    <button @click="message = 'Hello Vue!'">设置消息</button>
  </div>
</template>

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

const count = ref(0)
const message = ref('')

const users = ref([
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
])

const selectedUser = ref(null)

const greet = (name) => {
  alert(`你好,${name}!`)
}

const selectUser = (user) => {
  selectedUser.value = user
  console.log('选中用户:', user)
}

const handleClickWithEvent = (event) => {
  console.log('事件对象:', event)
  console.log('目标元素:', event.target)
}
</script>

5. 双向绑定指令 v-model

5.1 基础双向绑定

vue 复制代码
<template>
  <div>
    <!-- 文本输入框 -->
    <input v-model="message" placeholder="输入消息">
    <p>输入的内容:{{ message }}</p>
    
    <!-- 多行文本 -->
    <textarea v-model="description" placeholder="输入描述"></textarea>
    <p>描述内容:{{ description }}</p>
    
    <!-- 复选框 -->
    <input type="checkbox" id="checkbox" v-model="checked">
    <label for="checkbox">选中状态:{{ checked }}</label>
    
    <!-- 多个复选框 -->
    <div>
      <input type="checkbox" id="apple" value="苹果" v-model="checkedFruits">
      <label for="apple">苹果</label>
      
      <input type="checkbox" id="banana" value="香蕉" v-model="checkedFruits">
      <label for="banana">香蕉</label>
      
      <input type="checkbox" id="orange" value="橙子" v-model="checkedFruits">
      <label for="orange">橙子</label>
      
      <p>选中的水果:{{ checkedFruits }}</p>
    </div>
    
    <!-- 单选按钮 -->
    <div>
      <input type="radio" id="male" value="男" v-model="gender">
      <label for="male">男</label>
      
      <input type="radio" id="female" value="女" v-model="gender">
      <label for="female">女</label>
      
      <p>选择的性别:{{ gender }}</p>
    </div>
    
    <!-- 下拉选择框 -->
    <select v-model="selectedCity">
      <option value="">请选择城市</option>
      <option value="北京">北京</option>
      <option value="上海">上海</option>
      <option value="广州">广州</option>
    </select>
    <p>选择的城市:{{ selectedCity }}</p>
  </div>
</template>

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

const message = ref('')
const description = ref('')
const checked = ref(false)
const checkedFruits = ref([])
const gender = ref('')
const selectedCity = ref('')
</script>

5.2 v-model 修饰符

vue 复制代码
<template>
  <div>
    <!-- .lazy 修饰符:change 时更新而不是 input -->
    <input v-model.lazy="lazyMessage" placeholder="失去焦点时更新">
    <p>lazy 值:{{ lazyMessage }}</p>
    
    <!-- .number 修饰符:自动转换为数字 -->
    <input v-model.number="numberValue" type="number" placeholder="输入数字">
    <p>类型:{{ typeof numberValue }},值:{{ numberValue }}</p>
    
    <!-- .trim 修饰符:自动去除首尾空格 -->
    <input v-model.trim="trimmedMessage" placeholder="自动去除空格">
    <p>原始值:"{{ trimmedMessage }}"</p>
  </div>
</template>

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

const lazyMessage = ref('')
const numberValue = ref(0)
const trimmedMessage = ref('')
</script>

6. 模板语法进阶用法

6.1 计算属性在模板中使用

vue 复制代码
<template>
  <div>
    <input v-model="firstName" placeholder="名">
    <input v-model="lastName" placeholder="姓">
    
    <!-- 直接在模板中使用表达式 -->
    <p>全名:{{ firstName + ' ' + lastName }}</p>
    
    <!-- 使用计算属性(推荐) -->
    <p>全名:{{ fullName }}</p>
    <p>反转全名:{{ reversedName }}</p>
    
    <!-- 条件渲染计算属性 -->
    <p v-if="hasLongName">名字很长</p>
    <p v-else>名字不长</p>
  </div>
</template>

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

const firstName = ref('')
const lastName = ref('')

const fullName = computed(() => {
  return firstName.value + ' ' + lastName.value
})

const reversedName = computed(() => {
  return fullName.value.split('').reverse().join('')
})

const hasLongName = computed(() => {
  return fullName.value.length > 10
})
</script>

6.2 模板中的方法调用

vue 复制代码
<template>
  <div>
    <input v-model="searchText" placeholder="搜索文本">
    
    <!-- 在模板中调用方法 -->
    <p>大写:{{ toUpperCase(searchText) }}</p>
    <p>字符数:{{ getTextLength(searchText) }}</p>
    <p>是否包含数字:{{ containsNumber(searchText) }}</p>
    
    <!-- 方法作为事件处理器 -->
    <button @click="clearText">清空</button>
    <button @click="randomText">随机文本</button>
  </div>
</template>

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

const searchText = ref('')

const toUpperCase = (text) => {
  return text.toUpperCase()
}

const getTextLength = (text) => {
  return text.length
}

const containsNumber = (text) => {
  return /\d/.test(text)
}

const clearText = () => {
  searchText.value = ''
}

const randomText = () => {
  const texts = ['Hello', 'Vue3', 'Template', 'Syntax']
  searchText.value = texts[Math.floor(Math.random() * texts.length)]
}
</script>

7. 实际应用示例

完整的待办事项应用

vue 复制代码
<template>
  <div class="todo-app">
    <h1>待办事项列表</h1>
    
    <!-- 添加新任务 -->
    <div class="add-todo">
      <input 
        v-model="newTodo" 
        @keyup.enter="addTodo"
        placeholder="输入新任务"
      >
      <button @click="addTodo">添加</button>
    </div>
    
    <!-- 筛选器 -->
    <div class="filters">
      <button 
        v-for="filter in filters" 
        :key="filter.value"
        :class="{ active: currentFilter === filter.value }"
        @click="currentFilter = filter.value"
      >
        {{ filter.name }}
      </button>
    </div>
    
    <!-- 任务列表 -->
    <ul class="todo-list">
      <li 
        v-for="todo in filteredTodos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
      >
        <input 
          type="checkbox" 
          v-model="todo.completed"
        >
        <span 
          :class="{ 'todo-text': true, 'completed-text': todo.completed }"
          @dblclick="todo.editing = true"
          v-if="!todo.editing"
        >
          {{ todo.text }}
        </span>
        <input 
          v-else
          v-model="todo.text"
          @blur="todo.editing = false"
          @keyup.enter="todo.editing = false"
          @keyup.esc="todo.editing = false"
          class="edit-input"
          v-focus
        >
        <button @click="removeTodo(todo.id)">删除</button>
      </li>
    </ul>
    
    <!-- 统计信息 -->
    <div class="stats">
      <p>总计:{{ todos.length }} 项</p>
      <p>已完成:{{ completedCount }} 项</p>
      <p>未完成:{{ remainingCount }} 项</p>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, nextTick } from 'vue'

// 自定义指令:自动聚焦
const vFocus = {
  mounted: (el) => el.focus()
}

// 数据
const newTodo = ref('')
const todos = ref([
  { id: 1, text: '学习 Vue 模板语法', completed: false, editing: false },
  { id: 2, text: '完成待办事项应用', completed: false, editing: false }
])

const currentFilter = ref('all')

const filters = [
  { name: '全部', value: 'all' },
  { name: '未完成', value: 'active' },
  { name: '已完成', value: 'completed' }
]

// 计算属性
const filteredTodos = computed(() => {
  switch (currentFilter.value) {
    case 'active':
      return todos.value.filter(todo => !todo.completed)
    case 'completed':
      return todos.value.filter(todo => todo.completed)
    default:
      return todos.value
  }
})

const completedCount = computed(() => {
  return todos.value.filter(todo => todo.completed).length
})

const remainingCount = computed(() => {
  return todos.value.length - completedCount.value
})

// 方法
const addTodo = () => {
  if (newTodo.value.trim()) {
    todos.value.push({
      id: Date.now(),
      text: newTodo.value.trim(),
      completed: false,
      editing: false
    })
    newTodo.value = ''
  }
}

const removeTodo = (id) => {
  todos.value = todos.value.filter(todo => todo.id !== id)
}
</script>

<style scoped>
.todo-app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.add-todo {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.add-todo input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.filters {
  margin-bottom: 20px;
}

.filters button {
  margin-right: 10px;
  padding: 5px 10px;
  border: 1px solid #ddd;
  background: white;
  cursor: pointer;
  border-radius: 4px;
}

.filters button.active {
  background: #007bff;
  color: white;
}

.todo-list {
  list-style: none;
  padding: 0;
}

.todo-list li {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
  gap: 10px;
}

.todo-list li.completed {
  opacity: 0.6;
}

.completed-text {
  text-decoration: line-through;
}

.edit-input {
  flex: 1;
  padding: 5px;
  border: 1px solid #007bff;
  border-radius: 4px;
}

.stats {
  margin-top: 20px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 4px;
}

.stats p {
  margin: 5px 0;
}
</style>

模板语法最佳实践

1. 命名规范

vue 复制代码
<template>
  <!-- 好的命名 -->
  <div class="user-profile">
    <h2 class="profile-title">{{ userProfile.name }}</h2>
    <p class="profile-email">{{ userProfile.email }}</p>
  </div>
  
  <!-- 避免的命名 -->
  <div class="div1">
    <h2>{{ data.name }}</h2>
    <p>{{ data.email }}</p>
  </div>
</template>

2. 性能优化

vue 复制代码
<template>
  <!-- 好的做法:使用 key -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
  
  <!-- 避免:没有 key 或使用 index 作为 key -->
  <ul>
    <li v-for="(item, index) in items" :key="index">
      {{ item.name }}
    </li>
  </ul>
</template>

3. 可读性优化

vue 复制代码
<template>
  <!-- 好的可读性 -->
  <div>
    <button 
      v-if="user.isAuthenticated"
      @click="logout"
      class="logout-btn"
    >
      退出登录
    </button>
    
    <button 
      v-else
      @click="showLoginModal"
      class="login-btn"
    >
      登录
    </button>
  </div>
  
  <!-- 避免:复杂表达式直接写在模板中 -->
  <div v-if="user && user.profile && user.profile.permissions && user.profile.permissions.includes('admin')">
    管理员功能
  </div>
</template>

总结

模板语法核心要点:

  1. 数据绑定{{ }}v-bind 让数据在模板中显示
  2. 条件渲染v-ifv-show 控制元素显示/隐藏
  3. 列表渲染v-for 遍历数组和对象
  4. 事件处理v-on 响应用户交互
  5. 双向绑定v-model 实现表单数据同步

学习建议:

  1. 循序渐进:先掌握基础语法,再学习高级用法
  2. 多练习:通过实际项目加深理解
  3. 注意性能 :合理使用 key,避免不必要的重新渲染
  4. 保持简洁 :复杂逻辑放到 script 中,模板保持简单

掌握了这些模板语法,你就能创建动态、交互性强的 Vue 应用了!

相关推荐
前端工作日常3 分钟前
H5 实时摄像头 + 麦克风:完整可运行 Demo 与深度拆解
前端·javascript
韩沛晓14 分钟前
uniapp跨域怎么解决
前端·javascript·uni-app
前端工作日常15 分钟前
以 Vue 项目为例串联eslint整个流程
前端·eslint
程序员鱼皮17 分钟前
太香了!我连夜给项目加上了这套 Java 监控系统
java·前端·程序员
该用户已不存在43 分钟前
这几款Rust工具,开发体验直线上升
前端·后端·rust
前端雾辰1 小时前
Uniapp APP 端实现 TCP Socket 通信(ZPL 打印实战)
前端
无羡仙1 小时前
虚拟列表:怎么显示大量数据不卡
前端·react.js
云水边1 小时前
前端网络性能优化
前端
用户51681661458411 小时前
[微前端 qiankun] 加载报错:Target container with #child-container not existed while devi
前端
武昌库里写JAVA1 小时前
使用 Java 开发 Android 应用:Kotlin 与 Java 的混合编程
java·vue.js·spring boot·sql·学习