Code前端首页关于Code前端联系我们

Vue3到底有哪些新特性值得学?怎么快速上手?

terry 1小时前 阅读数 15 #Vue

距离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会自动解包,不用加。

refreactive的区别

刚才说了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

computedwatch

这两个在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还可以写成可修改的形式,就是传一个对象进去,有getset方法:

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倍,主要体现在这几个方面:

  1. 响应式系统的重构:Vue2的响应式系统是用Object.defineProperty劫持对象的属性,这个方法有几个缺点——只能劫持已有的属性,新增或删除属性不能触发响应式;不能劫持数组的索引和长度;性能一般,因为要递归遍历整个对象,Vue3的响应式系统是用Proxy来实现的,Proxy可以劫持整个对象,包括新增和删除属性、数组的索引和长度,而且不需要递归遍历整个对象,只有在访问属性的时候才会递归,性能提升了很多。
  2. 编译时优化:Vue3的编译器比Vue2聪明多了,它会在编译时分析模板,做很多优化,
    • 静态提升:把模板里的静态内容(比如不会变化的文字、图片)提升到渲染函数外面,只渲染一次,不需要每次组件更新都重新渲染。
    • PatchFlag(补丁标记):给模板里的动态内容(比如{{ count }}v-bind:classv-if)加一个标记,告诉Vue更新的时候只需要检查有标记的内容,不需要遍历整个DOM树,大大提升了更新的速度。
    • 事件监听缓存:把模板里的事件监听器(比如@click="increment")缓存起来,不需要每次组件更新都重新创建一个新的函数。
  3. 打包体积的减小:Vue3采用了Tree Shaking(摇树优化)的支持,也就是说你只需要引入你用到的API,不需要引入整个Vue库,打包后的体积比Vue2小了很多——比如如果只用到了Composition API的核心API,打包后的体积只有Vue2的一半左右。

怎么快速从Vue2转Vue3?

如果你已经有Vue2的基础,转Vue3其实非常快,不用一下子学所有的新东西,按这个步骤来:

  1. 先学<script setup>语法糖和核心的Composition APIrefreactivecomputedwatchwatchEffect,这几个API能让你写Vue3的代码和写Vue2的差不多,但逻辑更清晰。
  2. 再学Teleport和Suspense:这两个都是解决具体痛点的,用到的时候再学也来得及,但学了之后会让你的代码更优雅。
  3. 了解Vue3的其他新特性:比如v-model的变化(Vue3里组件可以有多个v-model了,不用再用.sync修饰符)、v-forv-if的优先级变化(Vue3里v-if的优先级比v-for高了,所以最好不要同时用在同一个元素上)、provide/inject的增强(可以传递响应式数据了)、自定义渲染器(可以用来写跨端应用,比如WePY、UniApp就是基于这个的)等等,这些用到的时候再查文档就行。
  4. 用Vue CLI或者Vite创建一个Vue3项目练手:Vite是Vue3官方推荐的构建工具,比Vue CLI快很多——开发时几乎是秒开,打包也快,练手的时候可以先从简单的Todo List、计数器开始,然后再做一个电商项目或者博客项目,把学到的新特性都用上。
  5. 查看官方的迁移指南:如果你有一个Vue2的项目要升级到Vue3,官方有非常详细的迁移指南,告诉你哪些地方要改,怎么改,还有一个@vue/compat迁移构建,可以让你在Vue3里运行Vue2的代码,然后逐步替换成Vue3的写法。

Vue3的新特性不是为了改而改,而是为了解决Vue2里的痛点——Composition API解决了代码复用和逻辑组织的问题,Teleport解决了弹窗定位的问题,Suspense解决了异步加载的问题,性能提升让应用更快更流畅,打包体积减小让加载更快,如果你是前端开发,不管你现在用不用Vue3,都应该学一下,因为它代表了前端框架的发展趋势,而且转Vue3的成本其实很低,有Vue2的基础的话,几天就能上手,然后在项目里逐步替换成Vue3的写法就行。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门