Vue3

模板语法及新特性

这篇文章详细介绍了 Vue3 模板语法的新特性和变化,帮助初学者快速掌握 Vue3 组件开发的核心知识。

🎯 引言

Vue3 在模板语法上带来了不少新变化和优化,特别是在多根节点支持、v-model 的升级、插槽语法调整、指令优先级、属性透传、事件修饰符增强以及全新的 Teleport 和 Suspense 组件上。掌握这些内容,能让你写出更简洁高效的 Vue3 应用,也更好地理解 Vue3 的设计理念和进阶用法。


🌿 多根节点 & Fragment

Vue2 中,一个组件模板必须有且只有一个根节点,这是大家常遇到的限制。而在 Vue3 中,这个限制被取消了,支持多根节点,也就是说你可以直接写多个并列的标签,而不需要额外包一层元素。

<template>
    <header>这是头部</header>
    <main>这是主体内容</main>
    <footer>这是底部</footer>
</template>

多根节点的底层实现是利用了虚拟 DOM 中的 Fragment 概念,Vue3 会自动将多个根节点包裹成一个 Fragment,渲染时不会额外创建 DOM 元素,保持结构清晰。

这个特性让组件结构更灵活,减少了不必要的 wrapper 标签,样式和布局也更方便控制。

注意:使用多根节点时,必须注意属性透传 Attributes 问题,当没有接收父组件传入的参数时,Vue 无法知道你要挂载到哪个 dom 节点上。

🔄 v-model 语法升级

标准 v-model 写法

通过 modelValue 进行接收,update:modelValue 进行发送事件。你可以在 get/set 中加入逻辑,比如格式化、验证等。

<template>
    <input v-model="model" />
</template>

<script setup>
import { computed } from 'vue';
const props = defineProps({
    modelValue: String,
});

const emit = defineEmits(['update:modelValue']);

const model = computed({
    get() {
        return props.modelValue;
    },
    set(val) {
        emit('update:modelValue', val);
    },
});
</script>

defineModel 语法糖

极简写法:省去了手动定义 props、emit 和 computed。Vue 3.4+ 新增,低版本不支持。

<template>
    <input v-model="model" />
</template>

<script setup>
const model = defineModel();
</script>

多参数绑定

Vue3 支持多个 v-model 绑定不同的 prop,例如:

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

ChildComponent 需要声明对应的 prop 和事件:

<script setup>
const title = defineModel('title');
const content = defineModel('content');
</script>

🎭 插槽语法变化(具名/作用域)

Vue3 里插槽语法变化不大,但更推崇使用 <template #name> 语法替代 Vue2 的 slot="name",并且作用域插槽使用更简洁的 v-slot 简写法。

具名插槽

Vue2 传统写法:

<template>
    <ChildComponent>
        <template slot="header">这里是头部</template>
    </ChildComponent>
</template>

Vue3 推荐写法:

<template>
    <ChildComponent>
        <template #header>这里是头部</template>
    </ChildComponent>
</template>

作用域插槽

Vue3 作用域插槽语法也更简单:

<template>
    <ChildComponent>
        <template #default="{ user }">
            <p>用户名是:{{ user.name }}</p>
        </template>
    </ChildComponent>
</template>
# 符号是 v-slot 的语法糖,写起来更简洁,同时也更统一,方便阅读。

⚖️ v-if 和 v-for 的优先级变更

在 Vue3 中,v-if 和 v-for 的优先级规则发生了细微调整,主要是 v-if 优先级更高,避免一些复杂情况下的渲染混乱。

Vue2 中写法问题示例

<li v-for="item in list" v-if="item.visible">{{ item.name }}</li>

这里虽然能用,但逻辑上 v-if 会在 v-for 循环内部触发。

Vue3 推荐写法

<template v-for="item in list" v-if="item.visible">
    <li>{{ item.name }}</li>
</template>
避免同时在同一元素上混用 v-if 和 v-for,这样容易导致渲染逻辑复杂难以维护。

🧩 属性透传 Attributes

Vue3 组件默认支持属性透传,即父组件传入的未被组件声明的属性,会自动绑定到组件根节点上。

ParentCompoennt.vue
<ChildComponent id="foo" class="bar" />

如果 ChildComponent 没声明 idclass,这两个属性依然会透传到组件根标签。

ChildComponent.vue
<template>
    <div>
        <!-- id 和 class 会自动被绑定到这里 -->
    </div>
</template>

关闭属性透传

有时候你不希望属性自动绑定,可以使用 inheritAttrs: false

<script setup>
defineOptions({ inheritAttrs: false });
</script>

然后手动使用 v-bind="$attrs" 绑定到指定元素。

属性透传减少了大量重复代码,让组件使用更简单灵活,但也要注意不要滥用,避免属性混乱。

🔔 事件修饰符新特性

Vue3 对事件修饰符做了增强,其中比较实用的是对 .once.passive 修饰符的支持更加全面,同时新增了 .capture 修饰符。

<button @click.once="handleClick">点击一次</button>
<button @scroll.passive="handleScroll">滚动监听</button>
<button @click.capture="handleCapture">捕获阶段事件</button>
  • .once:事件只触发一次,后续无效。
  • .passive:告诉浏览器不调用 preventDefault,提升性能,通常用于滚动事件。
  • .capture:事件捕获阶段触发,而不是冒泡阶段。
合理使用事件修饰符,能提升页面性能和事件响应的准确性。

🚀 Teleport 与 Suspense 使用

Teleport

Teleport 是 Vue3 新增的组件,用于把子组件的 DOM 结构传送到 DOM 树的指定位置,常用来实现模态框、弹出层等 UI。

<template>
    <Teleport to="body">
        <div class="modal">这是一个弹窗内容</div>
    </Teleport>
</template>

<style>
.modal {
    position: fixed;
    top: 30%;
    left: 50%;
    transform: translate(-50%, -30%);
    background: white;
    padding: 20px;
    border: 1px solid #ccc;
}
</style>
Teleport 让组件的 DOM 结构可以不受父组件层级限制,更灵活地控制弹窗、浮层位置。

Suspense

Suspense 用于等待异步组件或者异步数据加载完成再渲染,提升用户体验。

<template>
    <Suspense>
        <template #default>
            <AsyncComponent />
        </template>
        <template #fallback>
            <div>加载中...</div>
        </template>
    </Suspense>
</template>

<script setup>
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'));
</script>

这里 Suspense 会先显示 fallback 区域内容,异步组件加载完成后再显示默认插槽。

Suspense 对于异步加载场景非常友好,能更优雅处理加载状态。

🎨 CSS 中的动态样式绑定:v-bind

Vue 3 提供了一个特别的功能,就是可以在 <style> 标签里直接用 v-bind 绑定组件的响应式变量,自动帮你把变量值注入到样式中。这样,样式和数据状态保持同步,写法也非常简洁。

<template>
    <button @click="toggleColor">点击切换按钮颜色</button>
</template>

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

const isRed = ref(true);

function toggleColor() {
    isRed.value = !isRed.value;
}
</script>

<style scoped>
button {
    background-color: v-bind(isRed ? 'red': 'blue');
    color: white;
    border: none;
    padding: 10px 20px;
    cursor: pointer;
    font-size: 16px;
    border-radius: 4px;
    transition: background-color 0.3s;
}
</style>
v-bind 只能用于 <style scoped> 中,不能写在全局样式文件里。

🧾 小节总结

  • Vue3 支持多根节点和 Fragment,组件写法更灵活,无需包裹无用节点。
  • v-model 由原来的 value/input 模式升级为 modelValue/update:modelValue,支持多参数双向绑定。
  • 插槽语法使用 <template #name> 更简洁,作用域插槽写法也更清晰。
  • 事件修饰符支持 .once.passive.capture 等,提升事件处理性能。
  • Teleport 让组件元素传送到任意 DOM 位置,Suspense 让异步组件加载更友好。

❓ 知识问答(Q&A)

Q:Vue3 中为什么支持多根节点了?

A:因为 Vue3 引入了 Fragment 概念,虚拟 DOM 可以处理多个根节点,不再强制组件只有一个根节点,代码结构更灵活。

Q:v-model 在 Vue3 中的事件名和 prop 名称是什么?

A:事件名是 update:modelValue,prop 名称是 modelValue

Q:Teleport 有什么实际应用场景?

A:常见用于模态框、弹出层、通知等需要脱离父组件层级的 UI 元素。


🧪 小练习

练习 1

创建一个多根节点的 Vue3 组件,包含头部、主体和底部区域,使用 Fragment 结构,不添加额外包裹元素。

<template>
    <!-- 请在这里添加你的代码 -->
</template>

练习 2

实现一个支持两个 v-model 的自定义输入组件,分别双向绑定 titlecontent


🎉 恭喜你已经掌握 Vue3 模板语法与功能变化里的多根节点、v-model 升级、插槽写法优化、指令优先级调整、属性透传、事件修饰符增强,以及 Teleport 和 Suspense 使用技巧啦!祝你前端开发之路越走越顺!