VUE3.5版本做了哪些改动

908 阅读8分钟

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 样式标签上设置 nonce attribute。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.valuenull,使用 Shadow DOM 可以有效封装组件的样式,避免样式冲突。需要根据当前自定义组件的使用修改写法,该案例只是为了说明shadowRoot的作用。

2.7 服务端渲染 API

新增data-allow-mismatch

可以消除激活不匹配警告的特殊 attribute。

<div data-allow-mismatch="text">{{ data.toLocaleString() }}</div>

值可以限制不匹配为特定类型。允许的值有:

  • text
  • children (仅允许直接子组件不匹配)
  • class
  • style
  • attribute

如果没有提供值,则会允许所有类型的不匹配。

异步组件之惰性激活

首先这是服务端渲染适用。 在 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 应用外部的其他地方。 image.png

在 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函数、pauseresume方法、watchdeep选项支持传入数字、Teleport组件新增defer延迟属性、useTemplateRef函数等,这些提升在特定场景下还是挺有用的。vue也在不断进步,我们个人也应当如此。Day day up! 加油。