Vue 3.5 新特性盘点 + 使用感受

2024-09-14
18777 分钟
...

这次更新的吸引力没有 3.4 那么大,首先最大的更新就是这个 props 解构了,这是一个编译器特性,能够追踪 props 结构出来的变量,然后把用到的地方自动添加 props. 前缀。

const { count = 0, msg = 'hello' } = defineProps<{
  count?: number
  message?: string
}>()

function handle() {
    doSomething(count);
}

上面的代码等价于:

const props = withDefaults(defineProps<{
    count?: number;
    message?: string;
}>(), {
    count: 0,
    msg: "hello",
});

function handle() {
    doSomething(props.count);
}

这个语法糖看似挺有用的,但细想一下就是个鸡肋。首先已经有 toRefs 可以将 props 转成一堆 ref,这次的新写法只是省了几个 props.,外加少创建几个 ref 对象而已,性能也没啥区别。

其次如果组件大一点,那么 setup 里头的变量和函数会相当多,这解构又把一堆变量从 props 命名空间拿到了顶层,搞得名字更混乱了,想必大家都知道给变量起名字是多烦人的事情。

最后该语法糖并不能满足所有的场景,在 Vue 的公告里也有提到这样的代码:

const { count = 0 } = defineProps<{ count?: number }>()


watch(count )


watch(() => count )

这就有点违反直觉了,要知道用 toRefs 解构 props 的话是可以直接监视解构出来的 ref。之所以这样是因为它会编译为 watch(props.count, ...)props.count是取出来的值而不是响应对象,无法监听。

新的 useId 函数返回一个实例级别的唯一 ID,这东西的感觉还是 SSR 才用得到,因为一般客户端只创建一个 Vue 实例,想要唯一 ID 的话都是整个全局整数然后每次取完加一。

而服务端渲染的话就会多次创建实例,如果使用全局变量则每次渲染都是不同的值,可能造成混合失败、以及缓存失效,而 useId 则可以避免此问题。

加上了该属性的元素在客户端混合时能忽略与 SSR 不一致的内容,直接用客户端渲染的结果覆盖。

说到 SSR 中的不一致内容,我遇到的都是时间相关的,因为 HTTP 请求头中没有客户端的时间信息,后端取不到,最终渲染的结果跟客户端的就不一致。在本站的文章页就有这个问题:

这是因为文章右下角有个时间要格式化,而服务端无法得知客户端的时区,导致渲染结果不同,这并不是什么大问题,但有个错误看着总是难受。而新版可以在元素上设置 data-allow-mismatch 来忽略该错误。

<time
    data-allow-mismatch
    :datetime='date.toISOString()'
>
    {{ data.toLocaleString() }}
</time>

虽然这个属性只跟 Vue 有关,但却会渲染到 HTML 上,成为一个多余的东西,有代码洁癖的我看着是真不爽。

以往要对 watch 函数加清理过程可以这样写,比如取消未完成的请求:

import { watch, onBeforeUnmount } from "vue";

let controller = new AbortController();

watch(xxx, (newId) => {
    controller.abort(); 
    controller = new AbortController();

    fetch(`/api/${newId}`, { signal: controller.signal });
});


onBeforeUnmount(() => controller.abort());

这样写的问题是清理函数要写两遍,而且还要把controller放到顶层。有了新的 API 之后就可以这样了:

import { watch, onWatcherCleanup } from "vue";

watch(xxx, (newId) => {
    const controller = new AbortController();

    
    onWatcherCleanup(() => controller.abort());

    fetch(`/api/${newId}`, { signal: controller.signal });
});

onCleanup 的区别?

除此之外,watch 处理函数的最后一个参数可以接受一个回调,在清理时调用,这跟本次的新 API 功能是一样的,那么为什么还要加这个新函数呢?

我能想到的区别是解耦,就像 Composite API 和传统的选项 API 一样,新的写法支持将清理逻辑封装成可复用的函数,并同时注册多个清理函数。

记得 VueUse 里的很多函数返回的值都有暂停和恢复等方法,可以更细致的控制作用范围,此处更新中 Vue 自带的 watch 也支持这样做了。

export type WatchStopHandle = () => void;


export interface WatchHandle extends WatchStopHandle {
    pause: () => void;
    resume: () => void;
    stop: () => void;
}

在以前要暂停监视一段时间的话,要么取消然后再重新监视,要么搞个变量来跳过处理,不管怎样都要自己封一下,没法跟三方库组合。新版规范了暂停的接口,解决了这个问题。

<Teleport> 元素默认在挂载的时候就要拿到目标元素,这意味着如果挂载目标是它后面的元素,那渲染到它时还不存在,导致出错。新的 defer 属性指定 <Teleport> 在渲染完成后再去找目标元素,解决了这个问题。

<Teleport defer target="#container">...</Teleport>
<div id="container">后渲染的元素也能挂载到</div>

这功能我倒没用着,大部分情况用 <Teleport> 应该都是挂到全局节点,往组件里挂复杂度就高了不少。

本次更新的第二大功能非 useTemplateRef 莫属,简单来说该函数创建专门用于模版引用的 ref,可以动态决定元素绑到哪个 ref 上:

<template>
    <input type='text' :ref='refTarget' />
  <button @click="switchRef">切换</button>
</template>

<script setup lang="ts">
import { useTemplateRef, shallowRef } from "vue";

const refTarget = shallowRef("foo");
const fooEl = useTemplateRef<HTMLInputElement>("foo");
const barEl = useTemplateRef<HTMLInputElement>("bar");

function switchRef() {
  refTarget.value = refTarget.value === "foo" ? "bar" : "foo";
}
</script>

用法演示(掘金没法传视频)

像这样就能通过响应状态来决定<input>的 ref 是哪个,以前想实现同样的功能很是麻烦。

异步组件新增了一个 hydrate 属性,设为 hydrateOnVisible 使其仅在元素可见时才混合:

import { defineAsyncComponent, hydrateOnVisible } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  hydrate: hydrateOnVisible(),

  
  

  
  

  
  
})

这个 API 比较底层,我的项目里没有适用的场景,但可以解决一些性能问题。

众所周知 Vue 3 把响应式的部分单独搞成了一个库@vue/reactivity,这样任何人都能够用它来构建自己的框架,该库包含refreactivea以及相关的辅助函数,但唯独缺了watch

从设计上看,watch用于监听响应对象,与 Vue 是无关的,它工作在更底层所以应当由@vue/reactivity导出。但实际上在 3.5 以前它却放在@vue/runtime-core里,这次终于给挪过来了。

WatchOptionsdeep 参数现在支持设为整数,用来指定监听的深度。

const state = reactive({
  a: {
    b: {
      c: {
        d: {
          e: 1
        }
      }
    }
  }
})

watch(state, () => {
    console.log('state changed')
  },
  { flush: 'sync', deep: 2 }
)

state.a.b = { c: { d: { e: 2 } } } 
state.a.b.c = { d: { e: 3 } } 

本次优化据称降低了 56% 的内存占用,部分场景能达到 10 倍的性能提升,灵感来源于 Preact Signals 的链表实现。光凭这一点就该升级了,毕竟优化是白嫖的。

类型上也有写调整,比如 computed 支持 getter 和 setter 设为不同的类型

更新里还修复了 Custom Elements 的一堆问题,我没用到所以就不评价了。

这次的更新都是些小优化,没什么杀手级的特性,比起这些,我更期待的 Vapor、Suspense 也不知道今年能不能稳定。但毕竟没有 Breaking Change,升级还是无压力的。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
vue2025-10-22
shallowRef 与 ref 的区别、场景与坑位
结论先行: 会 深度 地把你放进去的对象转成响应式(递归代理),因此"对象内部的改动"也会触发视图更新。 只在 顶层 做依赖追踪: 更换 才触发更新 ;若你只是"在原对象上改属性",不会触发,除非手动 。 1\. 快速对比 维度 追踪粒度 深度(递归) 仅顶层 对象内部属性变更 会触发更新 不会 触发(除非 或重新赋值) 性能 有递归 & 依赖跟踪开销 更轻量,适合大对象/第三方实例 适用对象 普通标量、普通对象、数组 第三方实例(图表...
学习面试
vue2025-10-20
Vue 3 中的 setup执行时机与异步逻辑详解
Vue 3 中的 执行时机与异步逻辑详解 本文详细介绍 Vue 3 组合式 API 的核心函数 :它在组件生命周期中的执行时机、异步行为( 的影响)、常见用法和最佳实践。 一、 的执行时机 是 Vue 3 组件实例创建后、渲染前执行的函数。 它相当于 Vue 2 中的 和 的合体。 执行流程: 也就是说: 所有响应式数据( 、 、 )都在这里定义; 所有生命周期钩子( 、 等)都在这里注册; 模板渲染会等待 执行完成(或异步返回的 Pr...
学习面试
vue2025-10-08
vue中ref与reactive
Vue 3 的 与 :区别、实现,以及为什么更推荐 1\. 一句话结论 :适合 原始类型 或 独立、细粒度 的状态单元;类型推断明确、解构安全、追踪粒度更小, 默认首选 。 :适合管理 对象/集合 这类结构化状态;但 解构会丢失响应性 , 默认 非深度 ,需要正确的使用姿势配合。 2\. 用法与语义差异 2.1 基本示例 2.2 模板中的解包(unwrapping) 在 模板 里, 会 自动解包 ,可直接写 ,不必 。 在响应式对象中...
学习面试
vue2025-09-23
Vue3 中computed属性依赖总结
Vue3 属性依赖总结 1. 依赖收集机制 的 getter 内部 读取 到哪些响应式变量 ( / ),就会把这些变量收集为依赖。 当这些依赖发生变化时, 会重新计算。 2. 如果只返回常量 始终是 。 修改 或 不会触发 重新计算,因为 getter 内没有访问它们。 3. 如果访问了响应式变量但仍返回常量 初始化时会打印一次。 当 或 改变时,getter 会重新执行(再次打印),因为它们被收集为依赖。 但 依旧始终是 。 4. 总...
学习面试
vue2025-09-17
template标签为什么不可以使用v-show
前言 写 Vue 时,最容易踩的小坑之一,就是给 套一个 。编辑器不报错,页面却“消失”了。很多人以为这是 bug,其实它只是 Vue 的底层设计在默默提醒: 不是真节点,它只是个“草稿纸”。 一、为什么 拒绝 v show 的本质是切换元素的 CSS: 。浏览器要找到真实 DOM 节点,才能给它加样式。而 在运行时会被完全剥离,不会留下任何标签,Vue 找不到节点,也就无处下刀。 举个反面教材: 打开控制台,你会看到 还是生成了,但...
学习面试
vue2025-09-17
Vue 3中watchEffect自动追踪详解
Vue 3 自动追踪详解 一、是什么 是 Vue 3 Composition API 提供的一个响应式副作用(side effect)函数。 它会 立即执行一次回调函数 ,并且在回调函数中访问到的所有响应式数据(ref、reactive、computed 等)都会被 自动追踪 。 当这些依赖的响应式数据发生变化时,回调会重新执行。 简单理解: 能“自动发现”它用到的响应式变量,不需要手动指定依赖。 二、基本用法 特点: 第一次立即执行...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录