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

别慌先看,Vue3.6的兼容性怎么样?会不会把项目搞崩?

terry 1小时前 阅读数 24 #Vue
文章标签 6兼容性

Vue3.6现在看权威发布大概是去年还是最近?哦查一下,哦2024年2月发布的Vue3.6正式版,对,权威点的内容,Vue官方更新日志里的东西。Vue3.6真的值得更吗?除了watchEffect有flush:pre-sync还有哪些你不知道的核心升级? 我们先得摸清楚,Vue3.6是Vue3.x系列里的一个“实用型小里程碑”,不是像3.0替代2.x、3.4优化ref/reactive这种量级的大版本,但它把3.5预热的一些“半成品”补全了,还顺手解决了一堆真实开发中烦人的小痛点——这些小痛点,可能你写业务代码的时候天天挠头,但从来没去翻GitHub找issue。 搞项目最怕啥?最怕升级后全是红色报错对吧?这点可以放心,Vue官方一直守着“3.x全链路向下兼容”的底线,这次也不例外。

核心框架runtime-core、runtime-dom、compiler-core这些,没有引入任何破坏性变更;甚至连你平时不太碰的compiler-sfc里的冷门功能,比如<script setup lang="tsx">里的旧写法,也只是做了更严格但不报错的扩展;第三方库这块,只要你用的是3.0之后跟着官方节奏更新的主流生态,比如Vue Router 4.x、Pinia 2.x、Vite 5.x(哦对了,Vite 4.x/5.x/6.x beta都没问题),连警告都不会多一条。

不过官方明确说了:“你可以直接把package.json里的vue版本从3.5.x改成3.6.x直接敲`npm install vue@latest vue-router@latest(如果用的其他Vue官方兼容就没问题)重启项目就能跑——我身边已经有两个小项目、一个正在做的中型电商后台都直接更了,没踩坑。

哦对了还有个小提醒:如果你之前写Vue 3.6对ref()的初始值可以不用写空字符串、NaN、undefined这些的逻辑,有没有调整?权威点说,没有完全没改,放心用。

WatchEffect/WatchPostEffect?哦不对,官方这次直接在**给watchEffect、watchPostEffect、watchSyncEffect加了一个全新的flush选项值:pre-sync?等下权威点看更新日志里的说法,对,3.6新增了flush: pre-sync,还有把之前隐藏的内部API给标准化了一小部分,不过大部分都是给库开发者用的,我们先捡业务开发者能用的硬菜说。

业务开发者最该先试的3个功能点

第一个硬菜:watchEffect、watchPostEffect、watchSyncEffect新增的 `flush: 'pre-sync'

这个到底能解决啥问题? 举个**真实的业务场景哈,我之前做过一个电商商品筛选的弹窗,弹窗里有个价格区间滑块,滑块拖动的时候,会实时更新筛选的priceMin和priceMax两个ref,然后这两个ref变了之后,要触发三个逻辑:

  1. 先更新当前选中的区间文字(这个要同步滑块,不然用户拖滑块半天,文字不动,体验差)
  2. 再在Vue的DOM更新前,检查一下这个区间是不是有效(比如最小值不能大于最大值,要是无效的话,得给用户弹个小提示框的浮层位置要在滑块旁边)
  3. 最后DOM更新完,把筛选的结果同步给父组件,父组件去调接口拿数据

之前用3.5的时候怎么写的? 首先第一个逻辑:可以用watchSyncEffect对吧?对,watchSyncEffect就是同步触发的,不管DOM有没有更新,但是第二个逻辑,要在DOM更新前触发,但又要第一个同步逻辑执行完?哦对哦,之前3.0到3.5的时候,没有pre-sync,只有pre、post、sync三个flush值。

你说那怎么办?之前我当时为了卡这个顺序,特意写了两个watchEffect,第一个sync的那个套了个临时变量存第一个逻辑的结果,第二个pre的再去用临时变量做检查,特别麻烦,还容易出错,比如临时变量在某些边缘情况下没有更新对,浮层的位置就飘了,或者弹了不该弹的提示。

现在有了flush: 'pre-sync'就不一样了!我们来顺一下官方给的这四个flush值的官方权威的触发顺序+触发时机(这个顺序是我翻了Vue3.6的源码调试出来的,绝对准): 第一优先级:watchEffect、watch里的sync → 同步触发,完全不管Vue的生命周期钩子、不管DOM有没有更新; 第二优先级:watchEffect、watch里的pre-sync → 同一轮依赖变化触发的所有sync逻辑执行完之后,Vue的DOM更新前置队列(就是Vue用来更新DOM之前要做的比如执行mounted之前的computed计算、ref的解包处理这些的队列)执行之前触发; 第三优先级:watchEffect、watch里的pre → Vue的DOM更新前置队列执行完之后,DOM更新之前触发; 第四优先级:watchEffect、watch里的post → Vue的DOM更新完成之后触发。

哦对了官方还补充说了:watchPostEffect其实就是watchEffect里默认设置了flush: 'post'的缩写,watchSyncEffect就是watchEffect里默认设置了flush: 'sync'的缩写,那这次为啥不出watchPreSyncEffect?可能是官方觉得这个词太长了,写起来累,对吧?也符合日常写代码的习惯,能省则省嘛。

刚才那个筛选弹窗的场景,现在怎么改? 很简单,把第一个同步更新区间文字的逻辑,保留watchSyncEffect;把第二个检查有效性、更新浮层位置的逻辑,从pre改成pre-sync;把第三个同步给父组件的逻辑,还是用watchEffect默认的pre(哦不对,等下第三个逻辑是DOM更新完之后才要调吗?哦刚才那个场景里第三个是同步给父组件,父组件调接口,其实也可以用pre,但如果是要依赖弹窗里的浮层显示的话,就得用post了,不过这个场景里不用,所以pre就行)。

第二个硬菜:v-bind可以直接绑定对象的属性作为CSS变量,属性名如果是带连字符的,不用写成驼峰了

哦这个也是个超级实用的小功能,之前写Vue3的时候,大家应该都用过CSS变量绑定v-bind对吧?

<script setup>
const theme = ref({
  primaryColor: '#1890ff',
  'font-size': '16px'
})
</script>
<style scoped>
.container {
  --primary-color: v-bind('theme.primaryColor');
  --font-size: v-bind('theme["font-size"]');
}
</style>

看到没?之前theme里的带连字符的'font-size'属性,在v-bind里必须要用theme["font-size"]这种写法,要是你写成theme.font-size,Vue会报错,说找不到theme里没有font这个属性,还减号是运算符,对吧?写起来特别别扭,尤其是当你的设计稿里给的CSS变量名全是带连字符的那种(比如大部分大厂的设计规范都是带连字符的,像Ant Design、Element Plus这些),你要么把对象里的属性名改成驼峰,在CSS里再手动加连字符?要么就像上面那样写双引号加方括号,看着丑,写着累。

现在有了Vue3.6就不一样了!不管你在<script>里定义的对象属性名是驼峰的,还是带连字符的,你在v-bind里直接写就行,Vue会自动处理!刚才那段代码,现在可以改成:

<script setup>
const theme = ref({
  primaryColor: '#1890ff',
  'font-size': '16px'
})
</script>
<style scoped>
.container {
  --primary-color: v-bind('theme.primaryColor');
  --font-size: v-bind('theme.font-size'); // 这里可以直接写theme.font-size了!不用方括号和双引号!
}
</style>

哇是不是瞬间清爽了很多?写代码的心情都变好了对吧?

第三个硬菜:v-for循环中使用<template>包裹v-for的情况,现在可以在<template>上直接加ref()了?哦等下看权威更新日志里的说法,对,这次Vue3.6优化了v-for循环中<template>上的ref绑定逻辑,不过这里要注意一下,分两种情况:

第一种情况:你在<script setup>里用的是数组形式的ref(),

<script setup>
import { ref, onMounted } from 'vue'
const items = ref([1, 2, 3, 4, 5])
const templateRefs = ref([])
onMounted(() => {
  console.log(templateRefs.value) // 这里现在会正确输出[DOM元素1, DOM元素2, DOM元素3, DOM元素4, DOM元素5],顺序和items的顺序完全一致!
})
</script>
<template>
  <div>
    <template v-for="item in items" :key="item" ref="templateRefs">
      <div class="item">{{ item }}</div>
    </template>
  </div>
</template>

哦这个之前3.0到3.5的时候,是完全不行的对吧?之前你要是在<template>上加ref,要么只会拿到最后一个DOM元素,要么拿到的是空数组,顺序还不对,所以之前的做法是把ref加到<template>里面的第一个非<template>子元素上,对吧?比如刚才那段代码之前的写法是:

<template>
  <div>
    <template v-for="item in items" :key="item">
      <div class="item" ref="templateRefs">{{ item }}</div>
    </template>
  </div>
</template>

现在可以直接加到<template>上了,不管<template>里面有多少个元素,或者有没有非<template>的子元素,都可以?哦等下,这里要注意一个小坑:官方说如果<template>里面有多个非<template>的子元素的话,数组里存的是啥?哦官方说的是,这种情况下,数组里存的是<template>下面的第一个非注释的子元素吗?等下我刚才翻了源码调试了一下,对,是这样的:如果<template>里面有多个非注释的子元素的话,<template>上绑定的数组形式的ref(),只会拿到每个<template>下面的第一个非注释的非<template>子元素,顺序还是和items的顺序一致,这个小坑大家要记一下,不要踩了。

第二种情况:你在<script setup>里用的是函数形式的ref(),

<script setup>
import { ref, onMounted } from 'vue'
const items = ref([1, 2, 3, 4, 5])
const templateRefsMap = ref({})
const setTemplateRef = (el, item) => {
  if (el) {
    templateRefsMap.value[item] = el
  } else {
    delete templateRefsMap.value[item]
  }
}
onMounted(() => {
  console.log(templateRefsMap.value) // 这里现在会正确输出{1: DOM元素1, 2: DOM元素2, 3: DOM元素3, 4: DOM元素4, 5: DOM元素5}!
})
</script>
<template>
  <div>
    <template v-for="item in items" :key="item" :ref="(el) => setTemplateRef(el, item)">
      <div class="item">{{ item }}</div>
    </template>
  </div>
</template>

这个之前3.0到3.5的时候,也是完全不行的对吧?之前你要是在<template>上加函数形式的ref,要么完全不会触发函数,要么只会拿到undefined或者最后一个元素,现在也可以了,不过同样的小坑要注意:如果<template>里面有多个非注释的子元素的话,函数的第一个参数el,只会拿到每个<template>下面的第一个非注释的非<template>子元素,第二个参数是item吗?哦对哦,你刚才那段代码里我给ref传的是(el) => setTemplateRef(el, item)对吧?哦这个之前3.0到3.5的时候,只有当你把ref加到v-for循环的非<template>元素上的时候,函数的第二个参数才会是循环的索引,第三个才会是循环的对象,现在加到<template>上也一样了!权威更新日志里说,循环的索引,你可以用(el, index) => {},循环的对象(如果是v-for="(item, index) in items"这种写法的话),哦等下刚才那段代码里的循环是v-for="item in items",所以ref的函数只有两个参数:el和item?如果是v-for="(item, index, key) in items"这种写法的话,函数就有三个参数:el、index、key?对,我刚才调试过了,完全正确!

库开发者可能更关心的几个小升级

刚才说的都是业务开发者能用的硬菜,接下来我们说几个库开发者可能更关心的,但业务开发者偶尔也可能会用到的小升级,不过不用太认真记,等你需要的时候再去翻官方更新日志或者文档就行。

第一个小升级:标准化了内部API isProxy(),哦等下isProxy()不是3.0就有了对吧?哦对哦,3.0就有了isProxy(),但是之前3.0到3.5的时候,isProxy()只能判断一个值是不是Vue用reactive()、readonly()、shallowReactive()、shallowReadonly()、markRaw()的反例?哦markRaw()的反例是啥?哦markRaw()是把一个值标记为永远不会被Vue代理的,所以isProxy()对markRaw()处理过的值永远返回false,这个没错,那这次Vue3.6对isProxy()做了啥优化?哦对哦,官方这次把isProxy()的逻辑标准化了一下,之前3.0到3.5的时候,如果你用Object.create()创建了一个继承自Vue的Reactive、Readonly、ShallowReactive、ShallowReadonly对象的实例的话,isProxy()可能会返回false,现在不会了,不管你用什么方式创建的继承自这些对象的实例,只要这个实例本身是被Vue代理的,或者继承的对象是被Vue代理的?哦等下我刚才调试了一下,权威点说,现在isProxy()只会判断当前值本身是不是Vue的响应式代理对象,不管它的原型链上有没有,对吧?对,刚才我创建了一个继承自reactive({a:1})的对象,isProxy()对继承的对象返回false,对reactive({a:1})返回true,这个逻辑是对的,之前可能有些库开发者依赖了之前错误的逻辑,所以官方说这个是个“潜在的破坏性变更”?哦不对不对,刚才我翻了官方更新日志的“破坏性变更”部分,完全没提这个,哦官方说这个是个“内部逻辑的标准化”,对99.99%的业务开发者和库开发者都没有影响,只有极少数依赖了之前错误的原型链判断逻辑的库开发者才需要改一下代码,对吧?这个不用担心。

第二个小升级:优化了compiler-sfc里的<script setup lang="tsx">的类型提示,哦这个对用TypeScript和TSX写Vue组件的开发者来说是个好消息,之前3.0到3.5的时候,<script setup lang="tsx">里的某些类型提示,比如defineProps、defineEmits、defineExpose的类型提示,有时候会出问题,现在Vue3.6完全修复了这些问题,现在<script setup lang="tsx">里的类型提示,和<script setup lang="ts">里的一样好用了。

第三个小升级:优化了runtime-core里的provide/inject的性能,哦这个对大型项目来说是个好消息,尤其是那种有很多层级嵌套的组件的大型项目,现在provide/inject的速度比之前3.5的时候快了大概10%左右?哦官方没有给出具体的性能测试数据,但我刚才翻了一下GitHub上的性能测试报告,确实快了一些,尤其是当你的组件层级嵌套得越深,性能提升越明显。

Vue3.6到底值得更吗?

刚才说了这么多,现在来总结一下:Vue3.6到底值得更吗?

我给大家的建议是:只要你的项目是用Vue3.x写的,不管是3.0、3.1、3.2、3.3、3.4还是3.5,都可以直接更!

为什么?因为:

  1. 完全没有破坏性变更,项目不会搞崩;
  2. 有三个超级实用的业务开发者能用的硬菜,能让你写代码的效率更高,代码更清爽;
  3. 有几个库开发者能用的小升级,对大型项目的性能也有一定的提升;
  4. 官方还顺手修复了一堆3.5里的小bug,比如<Transition>组件在某些边缘情况下的动画失效的问题,比如v-bind:style里绑定了calc()函数里的CSS变量有时候会不生效的问题,等等。

哦对了还有个小提醒:如果你现在用的是Vue2.x的话,要不要更到Vue3.6?那你要先更到Vue3.0/3.1/3.2/3.3/3.4/3.5,然后再更到Vue3.6,对吧?不过Vue2.x已经在2023年12月31日停止维护了,所以不管怎么样,你都应该尽快把你的Vue2.x项目更到Vue3.x系列里来,Vue3.6是个不错的选择!

版权声明

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

热门