Vue3到底有哪些新特性值得学?怎么快速上手?
距离Vue3正式发布已经过去好几年了,但现在还是有不少前端开发在纠结要不要从Vue2转过来,或者转过来不知道从哪里学起——毕竟官方文档那么厚,各种新名词Composition API、Teleport、Suspense满天飞,刚接触很容易懵,今天就用大家听得懂的大白话,聊聊Vue3里真正对日常开发有用的核心新特性,以及怎么用最快的速度把它们融入自己的项目里。
先搞懂Composition API为什么替代了Vue2的Options API?
很多人第一次听说Vue3,第一反应就是“听说Vue2的Options API被改了,变成Composition API了,会不会很难?”其实两者不是谁彻底取代谁的关系,Vue3到现在还完全兼容Vue2的写法,Composition API是官方推荐用于中大型项目的新写法,小项目用Options API甚至更顺手。
那为什么官方要推Composition API呢?举个简单的例子你就明白了:假设你要做一个电商商品详情页,功能有收藏功能、加购功能、倒计时功能、评论加载功能,用Vue2的Options API的话,收藏的逻辑会分散在data、methods、computed、watch里,加购的也是,倒计时的也是,评论的也是,如果这个页面后续要改收藏逻辑,你得在代码里翻好几个地方;如果要把倒计时复用给其他页面,还得单独抽一个mixin,但mixin有个致命问题——命名冲突和数据来源不透明,比如你抽了一个倒计时mixin,里面有个data叫count,刚好商品详情页自己的data也有个count,那mixin里的就会被覆盖,而且接手代码的人根本不知道这个count到底是自己写的还是mixin带的。
Composition API就解决了这个问题:你可以把同一个功能的所有逻辑(响应式数据、方法、计算属性、侦听器)都放在一起写,比如倒计时的都放一个useCountdown函数里,收藏的放一个useCollect函数里,需要复用的时候直接把这个函数引入就行,命名冲突也可以通过命名空间或者重命名参数来解决,数据来源一眼就能看出来。
核心的Composition API用法入门
不用一开始就看所有的API,先掌握这几个就能应付80%的场景了:
setup函数(或者<script setup>语法糖)
<script setup>是现在写Vue3最常用的语法,比原来的setup函数还要简单——你直接在<script>标签上加个setup属性,里面写的代码就相当于原来setup函数里的内容,而且变量、函数、组件不用return就能在模板里用,完全没有多余的代码。
比如写一个简单的计数器:
<template>
<div>
<p>当前计数:{{ count }}</p>
<button @click="increment">加1</button>
<button @click="decrement">减1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// ref用来定义基本类型的响应式数据,比如数字、字符串、布尔值
const count = ref(0)
// 函数直接写,不用放在methods里
const increment = () => {
// 注意!在JS/TS里修改ref定义的数据,要加.value
count.value++
}
const decrement = () => {
count.value--
}
</script>
这里的ref是什么呢?就是把普通的基本类型数据变成响应式的,因为Vue2的响应式是用Object.defineProperty劫持对象的属性,基本类型没有属性,所以只能通过包装成对象来处理,所以在JS/TS里访问要加.value,但在模板里Vue会自动解包,不用加。
ref和reactive的区别
刚才说了ref用来定义基本类型,那reactive就是用来定义引用类型的响应式数据的,比如对象、数组,而且修改的时候不用加.value,因为它本身就是一个被劫持的对象。
比如把刚才的计数器改成对象形式:
<template>
<div>
<p>当前计数:{{ state.count }}</p>
<button @click="state.increment">加1</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
// reactive定义引用类型
const state = reactive({
count: 0,
increment: () => {
state.count++
}
})
</script>
那有人会问了:能不能用ref定义引用类型?当然可以,ref内部如果检测到传入的是引用类型,会自动调用reactive来包装,所以两种都能用,但官方的建议是基本类型用ref,引用类型用reactive,这样代码更清晰,不过也有例外,比如你要整个替换这个引用类型的数据,比如把整个商品对象换成另一个,那用ref会更方便——直接product.value = newProduct就行,用reactive的话得一个个属性赋值,或者用Object.assign。
computed和watch
这两个在Vue2里就有,但Composition API里有更灵活的用法,而且也是放在一起写的。
computed用来定义依赖其他响应式数据的计算属性,和Vue2一样,有缓存,只有依赖的数据变化了才会重新计算。
比如写一个计算平方的属性:
<template>
<div>
<input v-model.number="base">
<p>平方:{{ square }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const base = ref(2)
// 计算属性也是一个ref,JS/TS里访问要加.value
const square = computed(() => {
return base.value * base.value
})
</script>
computed还可以写成可修改的形式,就是传一个对象进去,有get和set方法:
const fullName = computed({
get: () => {
return firstName.value + ' ' + lastName.value
},
set: (val) => {
[firstName.value, lastName.value] = val.split(' ')
}
})
watch用来侦听某个响应式数据的变化,和Vue2相比,增加了几个参数,比如immediate(是否在组件初始化时立即执行一次)、deep(是否深度侦听对象的属性变化),还有watchEffect(自动收集依赖,不需要指定侦听哪个数据,只要依赖的数据变化了就会执行)。
比如用watch侦听刚才的base:
watch(base, (newVal, oldVal) => {
console.log(`base从${oldVal}变成了${newVal}`)
}, {
immediate: true, // 初始化时也会打印
deep: true // 如果base是对象,这个参数才有用
})
watchEffect的用法更简单,不用指定base:
watchEffect(() => {
console.log(`base现在是${base.value}`)
})
什么时候用watch什么时候用watchEffect呢?如果你知道要侦听哪个具体的数据,就用watch;如果你不知道要侦听哪个,只要里面用到的响应式数据变化了就执行,就用watchEffect。
Teleport:终于不用怕弹窗、提示框的定位问题了!
这个新特性真的是解决了前端开发的一个大痛点——比如你要做一个全屏的弹窗,用Vue2的话,你只能把弹窗组件放在根组件或者App.vue里,因为如果放在某个嵌套很深的组件里,父组件的position: relative或者overflow: hidden会影响弹窗的定位,而且层级(z-index)也很难控制。
Vue3的Teleport就解决了这个问题,它可以把组件内部的某个DOM元素直接“传送”到页面的另一个位置,比如body标签下,这样就不会受父组件的样式影响了。
用法也超简单,只要用<Teleport>标签把要传送的内容包起来,加个to属性指定目标位置就行:
<template>
<div class="deep-nested-component">
<!-- 这里的内容在DeepNestedComponent里 -->
<h3>嵌套很深的组件</h3>
<button @click="showModal = true">打开弹窗</button>
<!-- 这里的内容会被传送到body下 -->
<Teleport to="body">
<div v-if="showModal" class="full-screen-modal">
<div class="modal-content">
<h3>全屏弹窗</h3>
<button @click="showModal = false">关闭</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
/* 这里的样式不会影响Teleport传送的内容,除非加上:deep()或者去掉scoped */
.deep-nested-component {
width: 200px;
height: 200px;
border: 1px solid #ccc;
padding: 20px;
/* 就算加了overflow: hidden也没关系 */
overflow: hidden;
}
</style>
<style>
/* 传送的内容的样式最好写在全局或者非scoped里 */
.full-screen-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
}
</style>
是不是很方便?再也不用为了弹窗的定位问题头疼了。
Suspense:优雅处理异步组件和异步数据加载
在Vue2里,如果要加载一个异步组件,或者要在组件初始化时加载异步数据(比如从接口拿商品详情),你得手动处理加载状态、错误状态,代码会比较乱,Vue3的Suspense就可以帮你优雅地处理这些问题。
Suspense需要配合两个插槽使用:#default(默认插槽,放异步加载的内容)和#fallback(后备插槽,放加载过程中显示的内容,比如加载动画、加载提示)。
配合异步组件
异步组件的定义方式也变了,Vue3里用defineAsyncComponent来定义:
<!-- 主组件 -->
<template>
<div>
<h3>商品列表</h3>
<!-- Suspense包裹异步组件 -->
<Suspense>
<!-- 默认插槽:异步组件加载完成后显示 -->
<template #default>
<ProductList />
</template>
<!-- 后备插槽:加载过程中显示 -->
<template #fallback>
<div>正在加载商品列表...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 定义异步组件,通过import()动态导入
const ProductList = defineAsyncComponent(() => import('./ProductList.vue'))
</script>
当ProductList.vue还没加载完成时,页面会显示“正在加载商品列表...”,加载完成后就会显示ProductList。
配合组件内的异步数据加载
这个用法更实用,比如在商品详情页组件里,从接口拿数据,你可以在setup函数里返回一个Promise,或者用async/await(不过<script setup>里可以直接用async/await):
<!-- ProductDetail.vue -->
<template>
<div>
<h3>{{ product.name }}</h3>
<p>价格:{{ product.price }}</p>
<p>库存:{{ product.stock }}</p>
</div>
</template>
<script setup>
// 模拟从接口拿数据
const getProductDetail = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: 'Vue3实战指南',
price: 99,
stock: 100
})
}, 2000)
})
}
// 直接用async/await,组件会变成异步组件
const product = await getProductDetail()
</script>
然后在主组件里用Suspense包裹它:
<!-- 主组件 -->
<template>
<div>
<h3>商品详情</h3>
<Suspense>
<template #default>
<ProductDetail />
</template>
<template #fallback>
<div>
<div class="loading-spinner"></div>
<p>正在加载商品详情...</p>
</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const ProductDetail = defineAsyncComponent(() => import('./ProductDetail.vue'))
</script>
<style>
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #42b983;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
这样当接口数据还没回来时,页面会显示加载动画和提示,数据回来后就会显示商品详情,而且如果异步加载出错了,Vue3还提供了onErrorCaptured钩子或者ErrorBoundary组件(Vue3.2+内置了一个实验性的ErrorBoundary,不过现在还不太稳定,一般用onErrorCaptured)来处理错误状态。
Vue3的性能提升到底有多大?
很多人转Vue3除了新特性之外,还有一个重要原因就是性能提升,官方说Vue3比Vue2快1.2到2倍,主要体现在这几个方面:
- 响应式系统的重构:Vue2的响应式系统是用
Object.defineProperty劫持对象的属性,这个方法有几个缺点——只能劫持已有的属性,新增或删除属性不能触发响应式;不能劫持数组的索引和长度;性能一般,因为要递归遍历整个对象,Vue3的响应式系统是用Proxy来实现的,Proxy可以劫持整个对象,包括新增和删除属性、数组的索引和长度,而且不需要递归遍历整个对象,只有在访问属性的时候才会递归,性能提升了很多。 - 编译时优化:Vue3的编译器比Vue2聪明多了,它会在编译时分析模板,做很多优化,
- 静态提升:把模板里的静态内容(比如不会变化的文字、图片)提升到渲染函数外面,只渲染一次,不需要每次组件更新都重新渲染。
- PatchFlag(补丁标记):给模板里的动态内容(比如
{{ count }}、v-bind:class、v-if)加一个标记,告诉Vue更新的时候只需要检查有标记的内容,不需要遍历整个DOM树,大大提升了更新的速度。 - 事件监听缓存:把模板里的事件监听器(比如
@click="increment")缓存起来,不需要每次组件更新都重新创建一个新的函数。
- 打包体积的减小:Vue3采用了Tree Shaking(摇树优化)的支持,也就是说你只需要引入你用到的API,不需要引入整个Vue库,打包后的体积比Vue2小了很多——比如如果只用到了Composition API的核心API,打包后的体积只有Vue2的一半左右。
怎么快速从Vue2转Vue3?
如果你已经有Vue2的基础,转Vue3其实非常快,不用一下子学所有的新东西,按这个步骤来:
- 先学
<script setup>语法糖和核心的Composition API:ref、reactive、computed、watch、watchEffect,这几个API能让你写Vue3的代码和写Vue2的差不多,但逻辑更清晰。 - 再学Teleport和Suspense:这两个都是解决具体痛点的,用到的时候再学也来得及,但学了之后会让你的代码更优雅。
- 了解Vue3的其他新特性:比如
v-model的变化(Vue3里组件可以有多个v-model了,不用再用.sync修饰符)、v-for和v-if的优先级变化(Vue3里v-if的优先级比v-for高了,所以最好不要同时用在同一个元素上)、provide/inject的增强(可以传递响应式数据了)、自定义渲染器(可以用来写跨端应用,比如WePY、UniApp就是基于这个的)等等,这些用到的时候再查文档就行。 - 用Vue CLI或者Vite创建一个Vue3项目练手:Vite是Vue3官方推荐的构建工具,比Vue CLI快很多——开发时几乎是秒开,打包也快,练手的时候可以先从简单的Todo List、计数器开始,然后再做一个电商项目或者博客项目,把学到的新特性都用上。
- 查看官方的迁移指南:如果你有一个Vue2的项目要升级到Vue3,官方有非常详细的迁移指南,告诉你哪些地方要改,怎么改,还有一个
@vue/compat迁移构建,可以让你在Vue3里运行Vue2的代码,然后逐步替换成Vue3的写法。
Vue3的新特性不是为了改而改,而是为了解决Vue2里的痛点——Composition API解决了代码复用和逻辑组织的问题,Teleport解决了弹窗定位的问题,Suspense解决了异步加载的问题,性能提升让应用更快更流畅,打包体积减小让加载更快,如果你是前端开发,不管你现在用不用Vue3,都应该学一下,因为它代表了前端框架的发展趋势,而且转Vue3的成本其实很低,有Vue2的基础的话,几天就能上手,然后在项目里逐步替换成Vue3的写法就行。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



