这应该是全网最详细的Vue3.5版本解读
前言
Vue3.5正式版
在这两天发布了,网上已经有了不少关于Vue3.5版本的解读文章。但是欧阳发现这些文章对3.5中新增的功能介绍都不是很全
,所以导致不少同学有个错觉
,觉得Vue3.5版本不过如此,选择跳过这个版本等下个大版本再去更新。所以欧阳写了这篇超级详细
的Vue3.5版本解读文章,小伙伴们可以看看在3.5版本中有没有增加一些你期待的功能。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
版本号
这次的版本号是天元突破红莲螺岩
,这是07年出的一个二次元动漫,欧阳是没看过的。在此之前我一直以为这次的版本号会叫黑神话:悟空
,可能悟空不够二次元吧。
响应式
响应式相关的内容主要分为:重构响应式、响应式props支持解构、新增onEffectCleanup
函数、新增base watch
函数、新增onWatcherCleanup
函数、新增pause
和resume
方法。
重构响应式
这次响应式的重构是属于Vue内部优化,对于普通开发者来说是无感的。重构后内存占用减少了56%,优化手段主要是通过版本计数
和双向链表数据结构
,灵感来源于Preact signals。后续欧阳会出一系列关于响应式相关的源码文章,大家可以关注一波欧阳。
响应式props支持解构
在3.5中响应式props支持解构终于正式稳定了,在没有这个功能之前我们想要在js中访问prop必须要这样写:props.name
,否则name
将会丢失响应式。
有了响应式props解构后,在js中我们就可以直接解构出name
来使用,比如下面这样的代码:
<script setup lang="ts">
const { name } = defineProps({
name: String,
});
console.log(name);
</script>
当defineProps
搭配解构一起使用后,在编译时就可以将name
处理成props.name
。编译后简化的代码如下:
setup(__props) {
console.log(__props.name);
const __returned__ = {};
return __returned__;
}
从上面的代码可以看到console.log(name)
经过编译后变成了console.log(__props.name)
,这样处理后name
当然就不会丢失响应式了。
新增onEffectCleanup函数
在组件卸载之前或者下一次watchEffect
回调执行之前会自动调用onEffectCleanup
函数,有了这个函数后你就不需要在组件的beforeUnmount
钩子函数去统一清理一些timer了。比如下面这个场景:
import { watchEffect, ref } from "vue";
import { onEffectCleanup } from "@vue/reactivity";
const flag = ref(true);
watchEffect(() => {
if (flag.value) {
const timer = setInterval(() => {
// 做一些事情
console.log("do something");
}, 200);
onEffectCleanup(() => {
clearInterval(timer);
});
}
});
上面这个例子在watchEffect
中会去注册一个循环调用的定时器,如果不使用onEffectCleanup
,那么我们就需要在beforeUnmount
钩子函数中去清理定时器。
但是有了onEffectCleanup
后,将clearInterval
放在他的回调中就可以了。当组件卸载时会自动执行onEffectCleanup
传入的回调函数,也就是会执行clearInterval
清除定时器。
还有一点值得注意的是onEffectCleanup
函数目前没有在vue
包中暴露出来,如果你想使用可以像我这样从@vue/reactivity
包中导入onEffectCleanup
函数。
新增base watch函数
我们之前使用的watch
函数是和Vue组件以及生命周期一起实现的,他们是深度绑定的,所以watch
函数代码的位置在vue源码中的runtime-core
模块中。
但是有的场景中我们只想使用vue的响应式功能,也就是vue源码中的reactivity
模块,比如小程序vuemini
。为此我们不得不将runtime-core
模块也导入到项目中,或者像vuemini
一样去手写一个watch函数。
在3.5版本中重构了一个base watch
函数,这个函数的实现和vue组件没有一毛钱关系,所以他是在reactivity
模块中。详情可以查看我之前的文章: Vue3.5新增的baseWatch让watch函数和Vue组件彻底分手
还有一点就是这个base watch
函数对于普通开发者来说没有什么影响,但是对于一些下游项目,比如vuemini
来说是和受益的。
新增onWatcherCleanup函数
和前面的onEffectCleanup
函数类似,在组件卸载之前或者下一次watch
回调执行之前会自动调用onWatcherCleanup
函数,同样有了这个函数后你就不需要在组件的beforeUnmount
钩子函数去统一清理一些timer了。比如下面这个场景:
import { watch, ref, onWatcherCleanup } from "vue";
watch(flag, () => {
const timer = setInterval(() => {
// 做一些事情
console.log("do something");
}, 200);
onWatcherCleanup(() => {
console.log("清理定时器");
clearInterval(timer);
});
});
和onEffectCleanup
函数不同的是我们可以从vue中import导入onWatcherCleanup
函数。
新增pause和resume方法
有的场景中我们可能想在“一段时间中暂停一下”,不去执行watch
或者watchEffect
中的回调。等业务条件满足后再去恢复执行watch
或者watchEffect
中的回调。在这种场景中pause
和resume
方法就能派上用场啦。
下面这个是watchEffect
的例子,代码如下:
<template>
<button @click="count++">count++</button>
<button @click="runner2.pause()">暂停</button>
<button @click="runner2.resume()">恢复</button>
</template>
<script setup lang="ts">
import { watchEffect } from "vue";
const count = ref(0);
const runner = watchEffect(() => {
if (count.value > 0) {
console.log(count.value);
}
});
</script>
在上面的demo中,点击count++
按钮后理论上每次都会执行一次watchEffect
的回调。
但是当我们点击了暂停按钮后就会执行pause
方法进行暂停,在暂停期间watchEffect
的回调就不会执行了。
当我们再次点击了恢复按钮后就会执行resume
方法进行恢复,此时watchEffect
的回调就会重新执行。
console.log
的结果如下图:
从上图中可以看到count
打印到4后就没接着打印了,因为我们执行了pause
方法暂停了。当重新执行了resume
方法恢复后可以看到count
又重新开始打印了,此时从8开始打印了。
不光watchEffect
可以执行pause
和resume
方法,watch
一样也可以执行pause
和resume
方法。代码如下:
const runner = watch(count, () => {
if (count.value > 0) {
console.log(count.value);
}
});
runner.pause() // 暂停方法
runner.resume() // 恢复方法
watch的deep选项支持传入数字
在以前deep
选项的值要么是false
,要么是true
,表明是否深度监听一个对象。在3.5中deep
选项支持传入数字了,表明监控对象的深度。
比如下面的这个demo:
const obj1 = ref({
a: {
b: 1,
c: {
d: 2,
e: {
f: 3,
},
},
},
});
watch(
obj1,
() => {
console.log("监听到obj1变化");
},
{
deep: 3,
}
);
function changeDeep3Obj() {
obj1.value.a.c.d = 20;
}
function changeDeep4Obj() {
obj1.value.a.c.e.f = 30;
}
在上面的例子watch
的deep
选项值是3,表明监听到对象的第3层。
changeDeep3Obj
函数中就是修改对象的第3层的d
属性,所以能够触发watch
的回调。
而changeDeep4Obj
函数是修改对象的第4层的f
属性,所以不能触发watch
的回调。
SSR服务端渲染
服务端渲染SSR主要有这几个部分:新增useId
函数、Lazy Hydration 懒加载水合、data-allow-mismatch
新增useId
函数
有时我们需要生成一个随机数塞到DOM元素上,比如下面这个场景:
<template>
<label :htmlFor="id">Do you like Vue3.5?</label>
<input type="checkbox" name="vue3.5" :id="id" />
</template>
<script setup lang="ts">
const id = Math.random();
</script>
在这个场景中我们需要生成一个随机数id
,在普通的客户端渲染中这个代码是没问题的。
但是如果这个代码是在SSR服务端渲染中那么就会报警告了,如下图:
上面报错的意思是服务端和客户端生成的id
不一样,因为服务端和客户端都执行了一次Math.random()
生成id
。由于Math.random()
每次执行的结果都不同,自然服务端和客户端生成的id
也不同。
useId
函数的作用就是为了解决这个问题。
当然useId
也可以用于客户端渲染的一些场景,比如在列表中我们需要一个唯一键,但是服务端又没有给我们,这时我们就可以使用useId
给列表中的每一项生成一个唯一键。
Lazy Hydration 懒加载水合
异步组件现在可以通过 defineAsyncComponent() API 的 hydrate 选项来控制何时进行水合。(欧阳觉得这个普通开发者用不上,所以就不细讲了)
data-allow-mismatch
SSR中有的时候确实在服务端和客户端生成的html不一致,比如在DOM上面渲染当前时间,代码如下:
<template>
<div>当前时间是:{{ new Date() }}</div>
</template>
这种情况是避免不了会出现前面useId
例子中的那种警告,此时我们可以使用data-allow-mismatch
属性来干掉警告,代码如下:
<template>
<div data-allow-mismatch>当前时间是:{{ new Date() }}</div>
</template>
Custom Element 自定义元素改进
这个欧阳也觉得平时大家都用不上,所以就不细讲了。
Teleport组件新增defer延迟属性
Teleport
组件的作用是将children中的内容传送到指定的位置去,比如下面的代码:
<div id="target"></div>
<Teleport to="#target">被传送的内容</Teleport>
文案被传送的内容
最终会渲染在id="target"
的div元素中。
在之前有个限制,就是不能将<div id="target">
放在Teleport
组件的后面。
这个也很容易理解DOM是从上向下开始渲染的,如果先渲染到Teleport
组件。然后就会去找id的值为target
的元素,如果找不到当然就不能成功的将Teleport
组件的子节点传送到target
的位置。
在3.5中为了解决这个问题,在Teleport
组件上新增了一个defer
延迟属性。
加了defer
延迟属性后就能将target
写在Teleport
组件的后面,代码如下:
<Teleport defer to="#target">被传送的内容</Teleport>
<div id="target"></div>
defer
延迟属性的实现也很简单,就是等这一轮渲染周期结束后再去渲染Teleport
组件。所以就算是target
写在Teleport
组件的后面,等到渲染Teleport
组件的时候target
也已经渲染到页面上了。
useTemplateRef
函数
vue3中想要访问DOM和子组件可以使用ref进行模版引用,但是这个ref有一些让人迷惑的地方。
比如定义的ref变量到底是一个响应式数据还是DOM元素?
还有template中ref属性的值明明是一个字符串,比如ref="inputEl"
,怎么就和script中同名的inputEl
变量绑到一块了呢?
3.5中的useTemplateRef
函数就可以完美的解决了这些问题。
这是3.5之前使用ref访问input输入框的例子:
<input type="text" ref="inputEl" />
const inputEl = ref<HTMLInputElement>();
这个写法很不符合编程直觉,不知道有多少同学和欧阳一样最开始用vue3时会给ref
属性绑定一个响应式变量。比如这样::ref="inputEl"
更加要命的是这样写还不会报错,就是inputEl
中的值一直是undefined
。
最后一番排查后才发现ref
属性应该是绑定的变量名称:ref="inputEl"
使用useTemplateRef
函数后就好多了,代码如下:
<input type="text" ref="inputRef" />
const inputEl = useTemplateRef<HTMLInputElement>("inputRef");
使用useTemplateRef
函数后会返回一个ref变量,useTemplateRef
函数传的参数是字符串"inputRef"
。
在template中ref
属性的值也是字符串"inputRef"
,所以useTemplateRef
函数的返回值就指向了DOM元素input输入框。这个比3.5之前的体验要好很多了,详情可以查看我之前的文章: 牛逼!Vue3.5的useTemplateRef让ref操作DOM更加丝滑
总结
对于开发者来说Vue3.5版本中还是新增了许多有趣的功能的,比如:onEffectCleanup
函数、onWatcherCleanup
函数、pause
和resume
方法、watch
的deep
选项支持传入数字、useId
函数、Teleport
组件新增defer
延迟属性、useTemplateRef
函数。
这些功能在一些特殊场景中还是很有用的,欧阳的个人看法还是得将Vue升到3.5。
关注公众号:【前端欧阳】,给自己一个进阶vue的机会
另外欧阳写了一本开源电子书vue3编译原理揭秘,看完这本书可以让你对vue编译的认知有质的提升。这本书初、中级前端能看懂,完全免费,只求一个star。
这应该是全网最详细的Vue3.5版本解读的更多相关文章
- 全网最详细的IDEA、Eclipse和MyEclipse之间于Java web项目发布到Tomcat上运行成功的对比事宜【博主强烈推荐】【适合普通的还是Maven方式创建的】(图文详解)
不多说,直接上干货! IDEA [适合公司业务]全网最详细的IDEA里如何正确新建[普通或者Maven]的Java web项目并发布到Tomcat上运行成功[博主强烈推荐](类似eclipse里同一个 ...
- 【适合公司业务】全网最详细的IDEA里如何正确新建【普通或者Maven】的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【多个子项目】并存)(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的Eclipse和MyEclipse里对于Java web项目发布到Tomcat上运行成功的对比事宜【博主强烈推荐】【适合普通的还是Maven方式创建的】(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 全网最详细的MyEclipse里如何正确新建普通的Java web项目并发 ...
- 全网最详细的IDEA里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(类似eclipse里同一个workspace下【一个子项目】并存)(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的Eclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在Myeclipse里,则是File -> new -> ...
- 全网最详细的MyEclipse里如何正确新建普通的Java web项目并发布到Tomcat上运行成功【博主强烈推荐】(图文详解)
不多说,直接上干货! 首先,大家要明确,IDEA.Eclipse和MyEclipse等编辑器之间的新建和运行手法是不一样的. 如果是在eclipse里,则是File -> new -> ...
- 全网最详细的Windows系统里Oracle 11g R2 Client(64bit)的下载与安装(图文详解)
不多说,直接上干货! 环境: windows10系统(64位) 最好先安装jre或jdk(此软件用来打开oracle自带的可视化操作界面,不装也没关系:可以安装plsql,或者直接用命令行操作) Or ...
- 全网最详细的Windows系统里Oracle 11g R2 Client客户端(64bit)安装后的初步使用(图文详解)
不多说,直接上干货! 前期博客 全网最详细的Windows系统里Oracle 11g R2 Client(64bit)的下载与安装(图文详解) 命令行方式测试安装是否成功 1) 打开服务(cmd— ...
- 全网最详细的Windows系统里Oracle 11g R2 Database(64bit)安装后的初步使用(图文详解)
不多说,直接上干货! 前期博客 全网最详细的Windows系统里Oracle 11g R2 Database(64bit)的下载与安装(图文详解) 命令行方式测试安装是否成功 1) 打开服务(cm ...
- 全网最详细的Windows系统里Oracle 11g R2 Database(64bit)的完全卸载(图文详解)
不多说,直接上干货! 前期博客 全网最详细的Windows系统里Oracle 11g R2 Database(64bit)的下载与安装(图文详解) 若你不想用了,则可安全卸载. 完全卸载Oracle ...
随机推荐
- iOS开发基础102-后台保活方案
iOS系统在后台执行程序时,有严格的限制,为了更好地管理资源和电池寿命,iOS会限制应用程序在后台的运行时间.然而,iOS提供了一些特定的策略和技术,使得应用程序可以在特定场景下保持后台运行(即&qu ...
- django信号中的条件判断不符合时如何提示错误并返回
在Django中,如果你在信号(Signal)处理函数中需要进行条件判断,如果条件不符合,你可以触发一个异常,并在视图或其他地方捕获这个异常,然后返回相应的错误提示. 以下是一个简单的例子,演示如何在 ...
- 数据仓库建模工具之一——Hive学习第四天
Hive的基本操作 1.3HIve的表操作(接着昨天的继续学习) 1.3.2 显示表 show tables; show tables like 'u*'; desc t_person; desc f ...
- 30K Star,最全面的PDF处理开源项目,你也可以拥有一个本地的PDF处理大全
大家好,我是程序猿DD 今天给大家推荐一个日常大概率能用上的开源项目:Stirling PDF 开源地址:https://github.com/Stirling-Tools/Stirling-PDF ...
- 题解:CF1971C Clock and Strings
题解:CF1971C Clock and Strings 题意 在上图的一个圆中,给予你四个点 \(a,b,c,d\),判断线段 \(ab\) 与线段 \(cd\) 是否相交. 思路 先设置一个字符串 ...
- nacos启动失败:No DataSource set
通过docker查看nacos的日志发现nacos好端端的突然不能用了 docker logs nacos 报错后说是no datasource set,我看了我在docker里的MySQL是正常启动 ...
- Bond4配置
Bongding聚合链路工作模式 > bond聚合链路模式共7种:0-6Mode > bond 0 负载均衡 轮询方式往每条链路发送报文,增加带宽和容错能力.容易出现数据包无序到达的问题, ...
- 使用 useState 管理响应式状态
title: 使用 useState 管理响应式状态 date: 2024/8/1 updated: 2024/8/1 author: cmdragon excerpt: 摘要:本文详细介绍了在Nux ...
- 14、Spring之基于注解的声明式事务
14.1.概述 14.1.1.编程式事务 事务功能的相关操作全部通过自己编写代码来实现: Connection conn = ...; try { // 开启事务:关闭事务的自动提交 conn.set ...
- 【RabbitMQ】12 日志监控 & 消息追踪
一.日志和监控 RabbitMQ日志存放目录 [root@localhost ~]# ll /var/log/rabbitmq/ 总用量 176 -rw-r-----. 1 rabbitmq rabb ...