1. VUE3.5版本时间线
vue的3.5版本更新记录在这里:github.com-changelog
从2024-04-29开始发布alpha版本(内部测试版),经过五个版本的alpha升级后,在2024-08-08发布beta版本(公开测试版),经过三个版本的beta版本升级后,于2024-08-29发布rc版本(候选版本),终于在2024-09-03发布stable版本。八青妹用时间线的方式表示下:
timeline
title vue3.5版本更新记录
2024-04-29始 : 3.5.0-alpha.1
: 3.5.0-alpha.2
: 3.5.0-alpha.3
: 3.5.0-alpha.4
: 3.5.0-alpha.5
2024-08-08始 : 3.5.0-beta.1
: 3.5.0-beta.2
: 3.5.0-beta.3
2024-08-29始 : 3.5.0-rc.1
2024-09-03始 : 3.5.0
vue3.4版本的的更新时间范围是 (2023-10-28 - 2024-08-15) 结束3.4版本的更新是在rc(候选版本)还有半个月就发布的时候。
这次的版本号是Tengen Toppa Gurren Lagann,翻译过来是天元突破红莲螺岩,这是07年出的一个二次元动漫,除了火影海贼王东京猫猫异人之下狐妖小红娘这些,听都没听过这个二次元动漫,哈哈!
2. 更新点
2.1 watchEffect()的改进
增加了暂停/恢复侦听器,在这之前只有stop停止监听。
const { stop, pause, resume } = watchEffect(() => {})
// 暂停侦听器
pause()
// 稍后恢复
resume()
// 停止
stop()
假设我们有一个简单的计数器应用,其中我们希望在特定情况下完全停止侦听。但是还想要在特定条件下暂停和恢复对计数器变化的侦听。就可以使用3.5的这一特性。
2.2 副作用清理
增加了onWatcherCleanup,注册一个清理函数,在当前侦听器即将重新运行时执行。只能在 watchEffect 作用函数或 watch 回调函数的同步执行期间调用 (即不能在异步函数的 await 语句之后调用)。使用方法分别如下:
import { onWatcherCleanup } from 'vue'
watchEffect(async () => {
const { response, cancel } = doAsyncWork(newId)
// 如果 `id` 变化,则调用 `cancel`,
// 如果之前的请求未完成,则取消该请求
onWatcherCleanup(cancel)
data.value = await response
})
import { onWatcherCleanup } from 'vue'
watch(id, async (newId) => {
const { response, cancel } = doAsyncWork(newId)
onWatcherCleanup(cancel)
data.value = await response
})
2.3 模版引用 useTemplateRef
在3.5版本之前只可以在组件挂载后才能访问模板引用。如果你想在模板中的表达式上访问 input,在初次渲染时会是 null。这是因为在初次渲染前这个元素还不存在。写法如下:
<script setup>
import { ref, watchEffect } from 'vue';
// 创建一个 ref 来引用 input 元素
const inputRef = ref(null);
watchEffect(() => {
if (inputRef.value) {
inputRef.value.focus(); // 聚焦输入框
}
});
</script>
<template>
<input ref="inputRef" />
</template>
useTemplateRef返回一个浅层 ref,其值将与模板中的具有匹配 ref attribute 的元素或组件同步。实现同样的效果写法如下:
<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('input')
onMounted(() => {
inputRef.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
在 Vue 3.5 之前,使用 ref API 是获取模板中 DOM 元素引用的标准方式。尽管实现逻辑与 Vue 3.5 的 useTemplateRef 类似,但在类型推导和语法上,useTemplateRef 提供了更优雅的解决方案。
2.4 useId()
useId() API简化了在 Vue 3.5 中生成唯一标识符的过程,确保了在不同渲染环境下的唯一性和一致性。
如果同一页面上有多个 Vue 应用实例(如果你正在使用 Vue 来增强服务端渲染 HTML,并且只想要 Vue 去控制一个大型页面中特殊的一小部分,应避免将一个单独的 Vue 应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去),可以通过 app.config.idPrefix 为每个应用提供一个 ID 前缀,以避免 ID 冲突。
app.config.idPrefix = 'my-app'
// 在组件中:
const id1 = useId() // 'my-app:0'
const id2 = useId() // 'my-app:1'
2.5 响应式 Props 解构
在 Vue 3.5 及以上版本中,从 defineProps 返回值解构出的变量是响应式的。当在同一个 <script setup> 块中的代码访问从 defineProps 解构出的变量时,Vue 的编译器会自动在前面添加 props.
const { foo } = defineProps(['foo'])
watchEffect(() => {
// 在 3.5 之前仅运行一次
// 在 3.5+ 版本中会在 "foo" prop 改变时重新运行
console.log(foo)
})
2.6 自定义元素改进
definecustomelement新增选项
defineCustomElement 方法来支持创建自定义元素。这个方法接收的参数和 defineComponent 完全相同。但它会返回一个继承自 HTMLElement 的自定义元素构造器。
<my-vue-element></my-vue-element>
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// 这里是同平常一样的 Vue 组件选项
props: {},
emits: {},
template: `...`,
// defineCustomElement 特有的:注入进 shadow root 的 CSS
styles: [`/* inlined css */`]
})
// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签
// 都会被升级
customElements.define('my-vue-element', MyVueElement)
// 你也可以编程式地实例化元素:
// (必须在注册之后)
document.body.appendChild(
new MyVueElement({
// 初始化 props(可选)
})
)
以上是3.5版本之前已有的功能,在3.5版本,增加了三个自定义元素的选项:
configureApp:一个函数,可用于配置自定义元素的 Vue 应用实例。shadowRoot:boolean,默认为true。设置为false以在不带 shadow root 的情况下渲染自定义元素。这意味着自定义元素单文件组件中的<style>将不再被封装隔离。nonce:string,如果提供,将在注入到 shadow root 样式标签上设置nonceattribute。nonce值应该是随机生成的,并在每次请求中唯一,以提高安全性。(在需要保护的环境(例如,使用 内容安全策略CSP,跟服务器的配置相关)中,使用nonce可以确保只有信任的样式和脚本被执行)。
useHost()
在自定义组件内部,可以通过 useHost() API 或其他方法(如 $el)访问宿主元素。这使得组件能够直接与自己的宿主元素交互,例如修改样式、添加事件监听器等。举个例子:
<script setup>
import { ref, useHost } from 'vue';
const host = useHost(); // 获取宿主元素的引用
const isHighlighted = ref(false);
const toggleHighlight = () => {
if (host.value) {
isHighlighted.value = !isHighlighted.value;
// 根据状态添加或移除样式
host.value.style.backgroundColor = isHighlighted.value ? 'yellow' : '';
}
};
</script>
如果这个组件被用作 <my-component></my-component>,那么 <my-component> 就是宿主元素,该案例是根据组件的状态动态修改宿主元素的样式。需要根据当前自定义组件的使用修改写法,该案例只是为了说明宿主元素是啥。
注意,如果不是自定义组件会报错:
useHost can only be used in components defined via defineCustomElement
this.$host
一个选项式 API 的 property,暴露当前 Vue 自定义元素的宿主元素。确保在组件的生命周期中适时访问 this.$host,例如在 mounted 钩子或交互事件中使用。
在组合式API中,还是用上面提到的 useHost() API。
useShadowRoot
在自定义组件内部,可以通过useShadowRoot()返回当前 Vue 自定义元素的 shadow root。
<script setup>
import { ref, useShadowRoot } from 'vue';
const shadowRoot = useShadowRoot(); // 获取 Shadow DOM 的引用
const content = ref([]);
const addContent = () => {
if (shadowRoot.value) {
// 向 Shadow DOM 添加内容
const newItem = document.createElement('div');
newItem.textContent = `Item ${content.value.length + 1}`;
newItem.style.color = 'blue';
shadowRoot.value.appendChild(newItem); // 将新元素添加到 Shadow DOM
content.value.push(newItem.textContent); // 更新内容数组
}
};
</script>
<style>
/* 组件的样式将不会影响 Shadow DOM 内的样式 */
</style>
使用 useShadowRoot() 获取当前自定义组件的 Shadow DOM 引用。如果组件没有创建 Shadow DOM,则 shadowRoot.value 为 null,使用 Shadow DOM 可以有效封装组件的样式,避免样式冲突。需要根据当前自定义组件的使用修改写法,该案例只是为了说明shadowRoot的作用。
2.7 服务端渲染 API
新增data-allow-mismatch
可以消除激活不匹配警告的特殊 attribute。
<div data-allow-mismatch="text">{{ data.toLocaleString() }}</div>
值可以限制不匹配为特定类型。允许的值有:
textchildren(仅允许直接子组件不匹配)classstyleattribute
如果没有提供值,则会允许所有类型的不匹配。
异步组件之惰性激活
首先这是服务端渲染适用。 在 Vue 3.5+ 中,异步组件可以通过提供激活策略来控制何时进行激活。
- Vue 提供了一些内置的激活策略。这些内置策略需要分别导入,以便在未使用时进行 tree-shake。
- 该设计有意保持在底层,以确保灵活性。将来可以在此基础上构建编译器语法糖,无论是在核心还是更上层的解决方案 (如 Nuxt) 中实现。
异步组件可以通过 defineAsyncComponent() API 的 hydrate 选项指定策略.
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible()
})
Nuxt 团队已经在此功能的基础上构建了更高级别的语法糖。如果写服务端,用Nuxt体验会更好。
2.8 延迟解析的 Teleport
<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。例如下面使用<Teleport>实现一个模态框:
<template>
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
</template>
<script setup>
import { ref} from 'vue'
const open=ref(false);
</script>
从整个应用视图的角度来看,它在 DOM 中被渲染在整个 Vue 应用外部的其他地方。
在 Vue 3.5 及更高版本中,可以使用 defer prop 推迟 Teleport 的目标解析,直到应用的其他部分挂载。这允许 Teleport 将由 Vue 渲染且位于组件树之后部分的容器元素作为目标:
<Teleport defer to="#late-div">...</Teleport>
<!-- 稍后出现于模板中的某处 -->
<div id="late-div"></div>
目标元素必须与 Teleport 在同一个挂载/更新周期内渲染,即如果 <div> 在一秒后才挂载,Teleport 仍然会报错。延迟 Teleport 的原理与 mounted 生命周期钩子类似。
2.9 深层监听器
如果监听的数据源是对象,需要强制深度遍历,以便在深层级变更时触发回调,需要显式地加上 deep 选项,强制转成深层侦听器,写法如下:
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)
在 Vue 3.5+ 中,deep 选项还可以是一个数字,表示最大遍历深度——即 Vue 应该遍历对象嵌套属性的级数。
总结
完整的3.5记录可以看尤大大在官博上写的blog.vuejs.org/posts/vue-3…。
对于我们日常开发来说,onEffectCleanup函数、onWatcherCleanup函数、pause和resume方法、watch的deep选项支持传入数字、Teleport组件新增defer延迟属性、useTemplateRef函数等,这些提升在特定场景下还是挺有用的。vue也在不断进步,我们个人也应当如此。Day day up! 加油。