想搞懂Vue3的template怎么用?不管是刚入门的新手,还是想优化项目的老开发,这篇从基础语法、和Composition API配合,到性能优化的内容,把template的门道拆碎了讲清楚~
Vue3 template基础:结构、语法和核心指令
首先得明确,<template>
是Vue单文件组件(.vue文件)里的“HTML区域”,和<script>
(写逻辑)、<style>
(写样式)平级,它的作用是描述组件的HTML结构,再和逻辑、样式配合,变成最终渲染的页面。
### 结构自由:多根元素支持
Vue2时代,<template>
必须包一个根节点(比如一个大
### 插值表达式:{{ }}里的小世界
模板里最基础的“数据展示”方式是插值表达式,用双大括号把变量或简单JS表达式包起来。
```vue
欢迎{{ username }}! 当前计数:{{ count + 1 }}
### 核心指令:让模板“动”起来的魔法 Vue的指令(以v-开头)是模板和逻辑交互的关键,常用的有这些:
-
v-bind(简写:):绑定HTML属性,比如给img绑定src、给div绑定class/style:
<img :src="imgUrl" :class="{ active: isActive }">
-
v-on(简写@):绑定事件,点击、输入等交互全靠它:
<button @click="handleClick">点击触发</button>
-
v-if / v-else / v-show:条件渲染,v-if是“真删DOM”,v-show是“隐藏DOM(display: none)”:
<template v-if="isLogin"> <!-- template当逻辑容器,不渲染成DOM --> <p>欢迎回来</p> </template> <p v-else>请先登录</p> <div v-show="isVisible">我可以被隐藏</div>
-
v-for:列表循环,遍历数组/对象,记得加唯一key(避免DOM复用错误):
<ul> <li v-for="(item, index) in list" :key="item.id"> {{ index }} - {{ item.name }} </li> </ul>
和Composition API联动:让模板“活”起来的响应式逻辑
Vue3的Composition API(比如ref、reactive、computed)是写逻辑的核心,而template要做的是把这些响应式数据和方法“可视化”。
### ref:基础类型的响应式
用ref定义的基础类型(字符串、数字、布尔等),在模板里不用写.value(Vue自动解包)。
```vue
计数:{{ count }}
### reactive:对象/数组的响应式
reactive用来让对象/数组变成“响应式容器”,模板里直接访问属性:
```vue
姓名:{{ user.name }} 技能:{{ user.skills.join('、') }}
### computed:衍生的响应式数据
计算属性用来处理“依赖其他数据,且需要缓存”的逻辑,比如根据count生成双倍数值:
```vue
原始值:{{ count }} 双倍值:{{ doubleCount }}
### 方法绑定:事件与逻辑的桥梁 setup里定义的函数,能直接绑定到模板的事件上,比如表单提交、列表删除: ```vue ``` v-model是语法糖,背后是v-bind:value + v-on:input,所以和响应式数据联动很丝滑。
template进阶玩法:插槽、动态组件与场景化实践
光有基础还不够,模板的“灵活性”得靠插槽(Slot)和动态组件来拓展。
### 插槽:让组件“可定制” 插槽是父组件向子组件传递HTML结构的方式,分三种场景:
-
基础插槽:子组件留一个“坑”,父组件填内容。
子组件MyCard.vue:<template> <div class="card"> <slot>默认内容</slot> <!-- 没传内容时显示默认 --> </div> </template>
父组件使用:
<MyCard> <h2>我是自定义标题</h2> <p>这是卡片内容</p> </MyCard>
-
具名插槽:子组件留多个“命名坑”,父组件精准填充。
子组件Layout.vue:<template> <header><slot name="header"></slot></header> <main><slot></slot></main> <!-- 匿名插槽(默认插槽) --> <footer><slot name="footer"></slot></footer> </template>
父组件使用:
<Layout> <template v-slot:header> <!-- 简写#header --> <h1>网站标题</h1> </template> <p>主体内容</p> <!-- 填默认插槽 --> <template #footer> <p>版权信息</p> </template> </Layout>
-
作用域插槽:子组件给插槽传数据,父组件按需渲染。
子组件TodoList.vue:<template> <ul> <li v-for="todo in todos" :key="todo.id"> <slot :todo="todo"></slot> <!-- 传当前todo给父组件 --> </li> </ul> </template> <script setup> import { ref } from 'vue' const todos = ref([ { id: 1, text: '学Vue3', done: false }, { id: 2, text: '写项目', done: false } ]) </script>
父组件使用:
<TodoList v-slot="slotProps"> <!-- slotProps接收子组件传的todo --> <input type="checkbox" v-model="slotProps.todo.done" /> <span :class="{ done: slotProps.todo.done }">{{ slotProps.todo.text }}</span> </TodoList> <style scoped> .done { text-decoration: line-through; } </style>
作用域插槽适合子组件控制数据,父组件控制UI的场景(比如组件库的表格、列表组件)。
### 动态组件:一键切换页面/功能
动态组件用<component :is="组件名">
实现“根据变量渲染不同组件”,典型场景是Tab切换、路由懒加载。
例子:Tab切换组件
<script setup> import Home from './Home.vue' import About from './About.vue' const currentTab = ref(Home) // 初始显示Home组件 function switchTab() { currentTab.value = currentTab.value === Home ? About : Home } </script> <template> <button @click="switchTab">切换页面</button> <component :is="currentTab"></component> </template>
如果是异步组件(比如路由组件),可以用defineAsyncComponent
懒加载:
const Home = defineAsyncComponent(() => import('./Home.vue'))
这样打包时会把Home单独拆分成一个chunk,加快首屏加载。
避坑+优化:让template既稳又快的技巧
写模板时,“能跑”和“跑得好”是两码事,这些技巧能帮你避开 bug、提升性能。
### 性能优化:减少不必要的渲染 - **key的正确使用**:v-for循环必须加唯一key!如果用index当key,列表增删、排序时会触发DOM复用错误(比如输入框内容错位),尽量用数据的唯一标识(如id): ```vue
-
v-if和v-for的优先级:Vue3中,v-if的优先级比v-for高,如果在同一个元素上同时写v-for和v-if,会先判断v-if,再执行v-for,这会导致“想循环部分数据”时逻辑错误,
<!-- 不推荐:先判断if,再循环(可能循环空数组) --> <li v-for="item in list" v-if="item.visible" :key="item.id">...</li>
正确做法是用计算属性先过滤数据,再循环:
<script setup> const list = ref([...]) const visibleList = computed(() => list.value.filter(item => item.visible)) </script> <li v-for="item in visibleList" :key="item.id">...</li>
-
静态提升:Vue3编译时会自动识别不依赖响应式数据的静态内容,并“提升”到渲染函数外,只渲染一次。
<template> <div class="static">我是静态文本</div> <div>{{ dynamic }}</div> </template>
第一个div是静态的,编译后只会渲染一次,后续更新时跳过它,开发者不用手动处理,了解这个机制就行。
### 常见Bug:这些“雷”别踩 - **模板解析错误**:比如标签没闭合(
-
响应式数据不更新:
- 用ref时,逻辑里没写.value(比如count++ 写成count.value++)。
- 用reactive时,直接替换整个对象(比如user = { name: '新名' }),导致响应式丢失,改用ref包对象,或修改属性(user.name = '新名')。
-
插槽传值不生效:
- 子组件没写slot标签,或具名插槽name写错。
- 父组件v-slot用错,作用域插槽要接收子组件传的参数(比如v-slot="props")。
-
动态组件加载失败:
- :is绑定的组件没在components里注册,或组件名大小写错误。
- 异步组件没加defineAsyncComponent,导致加载逻辑错误。
新手常问的template问题,一次性解答
### Q1:template里能写JS吗? A:插值表达式里能写简单JS表达式(如算术运算、三元判断、函数调用),但不能写语句(if、for、变量声明等),复杂逻辑建议放computed或methods里。
### Q2:v-show和v-if选哪个? A:如果元素频繁切换显示(比如弹窗、tab),用v-show(只是隐藏DOM,不销毁);如果切换频率低(比如用户权限控制),用v-if(销毁DOM,节省性能)。
### Q3:多根元素会影响样式吗? A:不会。