vue3.4新特性有哪些,通过案例让你看的明明白白

目录
文章目录隐藏
  1. defineModel 已稳定
  2. defineModel 的简单介绍
  3. 使用 $emit 更新父组件传递过来的数据(vue3.2)
  4. 使用 defineModel 更新父组件传递过来的数据(vue3.4)
  5. 使用 defineModel 后的感觉
  6. defineModel 传递多个 v-model
  7. defineModel 设置默认值
  8. 发现 2 个问题:父子数据不同步,子组件数据不更新
  9. 验证: 子组件引用数据类型不更新,基本数据类型会更新
  10. 总结: defineModel 使用默认值会造成 2 个影响
  11. 处理 v-model 修饰符
  12. v-model 修饰符实现:第一个字母转为大写
  13. v-bind 的简写语法
  14. v-bind 的简写语法用在组件传递值上
  15. 更高效的反应性系统 watchEffect
  16. 编译器性能优化
  17. 删除了已弃用的功能
  18. 监听子组件的生命周期:@vnode-xx 更改为@vue:xxx
  19. 监听子组件的生命(vue3.4 之前)
  20. 监听子组件的生命(vue3.4)
  21. 监听子组件的生命-奇怪的地方
  22. 奇怪的地方:如果使用 vue3 的生命周期将不会被触发
  23. v-is 指令已被删除,改用带 vue:前缀的 is 属性
  24. 结语

在本文中,通过最近在项目中实践,我将深入探讨 Vue 3.4 带来的革新和提升,一同见证这个前端框架的进步。

性能提升:

在性能方面,Vue 3.4 做出了重大的内部改进,其中最引人注目的是模板解析器的全新重写。与之前基于正则表达式的解析器相比,新的解析器使用基于状态机的标记化技术,它通过单次遍历整个模板字符串来解析模板,显著提高了解析速度,模板解析器速度提高了 2 倍。无论是小型还是大型的 Vue 模板,新解析器都能保持两倍的性能提升,同时确保了向后兼容性。

另一个值得关注的升级是对响应系统的重构。在旧版本中,即使计算属性的结果未发生变化,每次依赖项更新,观察者也会被触发。而在 Vue 3.4 中,优化后的系统确保只有在计算结果实际变化时,相关的效果才会被触发,从而减少了组件的不必要渲染,提升了整体的性能。

Vue 3.4 中引入了非常多的新特性,下面我列举一些来介绍它们,争取让大家看得明明白白的:

defineModel 已稳定

defineModel 在 vue3.3 中还是一个实验性功能,但是经过一个学期的努力,该同学已经转正,大家可以使用。

defineModel 的简单介绍

defineModel() 返回的值是一个 ref,它可以像其他ref一样被访问以及修改,能起到在父组件和当前变量之间的双向绑定的作用。它的.value和父组件的v-model的值同步,当它被子组件改变时,会触发父组件绑定的值一起更新。

我们都知道 props 的设计原则是单项数据流,子组件默认情况下是无法更改父组件传递过来的数据。如果要更改 vue3.3 以前是通过 $emit 来实现的。

下面我们来对比一下使用$emitdefineModel来更新数据。

使用 $emit 更新父组件传递过来的数据(vue3.2)

父组件:

// 父页面
<template>
  <div>
    <div class="father-box">
      <p>我是父页面-此时:son 组件的值{{ flag }}</p>
      <button @click="openHandler"> 显示子组件</button>
    </div>
    <!-- 控制子组件是否显示 -->
    <son v-model:flag="flag"></son>
  </div>
</template>
<script setup lang="ts">
// vue3.2 开始组件引入后,就不需要注册啦。变量也不需要暴露出去啦
import son from '@/components/son.vue'
import { ref } from 'vue';
let flag = ref(false)
// 点击事件,更改值,让组件显示出来
const openHandler = ()=>{
  flag.value = true;
}
</script>
<style>
.father-box{
  background: palegreen;
}
</style>

子组件:

// son 组件
<template>
  <div class="son-box" v-if="flag">
    <h1 >我是 son 组件</h1>
    <button @click="hideHandler"> 关闭组件:更改父组件传递过来的值</button>
  </div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
// 接收传递过来的值
defineProps({
  flag: {
    type: Boolean,
    default: false,
  }
});
// 注册事件
const emits = defineEmits(["update:flag"]);
// 去更新父组件中的 flag 值,更改为 false
const hideHandler = () => {
  emits("update:flag", false)
}
</script>
<style>
.son-box{
   background: pink;
}
</style>

使用 $emit 更新父组件传递过来的数据

使用 defineModel 更新父组件传递过来的数据(vue3.4)

父组件:

// 父页面代码
<template>
  <div>
    <div class="father-box">
      <p>我是父页面-此时:son1 组件的值{{ flag }}</p>
      <button @click="openHandler"> 显示子组件</button>
    </div>
    <!-- 控制子组件是否显示 -->
    <son1 v-model:flag="flag"></son1>
  </div>
</template>
<script setup lang="ts">
// vue3.2 开始组件引入后,就不需要注册啦。变量也不需要暴露出去啦
import son1 from '@/components/son1.vue'
import { ref } from 'vue';
let flag = ref(false)
// 点击事件,更改值,让组件显示出来
const openHandler = ()=>{
  flag.value = true;
}
</script>
<style>
.father-box{
  background: palegreen;
}
</style>

子组件:

// son1 组件
<template>
  <div class="son-box" v-if="flagBool">
    <h1 >我是 son1 组件</h1>
    <button @click="hideHandler"> 关闭组件:更改父组件传递过来的值</button>
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
// defineModel('flag')中的 flag 必须与传递过来的属性保持一致
// flagBool 是接受的控制变量,可以是任意的
const flagBool = defineModel('flag')
const hideHandler = () => {
  flagBool.value = false
}
</script>
<style>
.son-box{
   background: pink;
}
</style>

使用 defineModel 更新父组件传递过来的数据

使用 defineModel 后的感觉

现在使用 defineModel 进行数据的双向绑定更加友好,比原来更加方便了。

原来需要在一个合适的时机(事件触发)写上:emits("update:flag", false)

而现在直接写上const 变量名 = defineModel('双向绑定的值'),不需要考虑时机。

defineModel 传递多个 v-model

父组件:

// 父页面
<template>
  <div>
    <div class="father-box">
      <p>我是父页面-此时:son3 组件的值{{ titleName }} {{ address }}</p>
    </div>
    <!-- defineModel 传递多个 v-model -->
    <son3 v-model:titleName="titleName" v-model:address="address"></son3>
  </div>
</template>
<script setup lang="ts">
// vue3.2 开始组件引入后,就不需要注册啦。变量也不需要暴露出去啦
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
let titleName = ref('少玩手机多看报')
let address = ref('我在这个红绿灯旁边')
</script>
<style>
.father-box{
  background: palegreen;
}
</style>

子组件:

// 组件 son3
<template>
  <div class="son-box">
    <h1 >我是 son1 组件</h1>
    <input type="text" v-model="titleName"> 
    <input type="text" v-model="address"> 
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
const titleName = defineModel('titleName')
const address = defineModel('address')
</script>
<style>
.son-box{
   background: pink;
}
</style>

defineModel 传递多个 v-model

defineModel 设置默认值

父组件:

// 父页面
<template>
  <div>
    <div class="father-box">
      <!-- 这里获取不到子组件的值 -->
      <p>我是父页面-此时:son3 组件的值{{ obj }} </p>
    </div>
    <!-- 没有给子组件传递值 -->
    <son3></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const obj = ref()
</script>
<style>
.father-box{
  background: palegreen;
}
</style>

子组件:

<template>
  <div class="son-box">
    <h1 >我是 son3 组件</h1>
    <h2>{{  detailsObj.name }}</h2>
    <h2>{{  detailsObj.age }}</h2>
    <button @click="changeHandler">改变值</button>
  </div>
</template>

<script setup lang="ts">
import { defineModel } from "vue";
// 定义 参数如类型、默认值
let detailsObj = defineModel('obj', {
  // 初始渲染的时候会显示默认值
  default: { name :'张三', age: 10},
  type: Object,
});
// 改变值
const changeHandler = ()=>{
  console.log(detailsObj)
  detailsObj.value.name = '我是王五',
  detailsObj.value.age = 20
}
</script>
.son-box{
   background: pink;
}
</style>

defineModel 设置默认值 defineModel 设置默认值

发现 2 个问题:父子数据不同步,子组件数据不更新

第 1 个问题是:我们通过给 defineModel 设置了默认值,子组件也正确显示了默认值,但是父页面获取不到子组件的值,这导致导致父组件与子组件之间的数据不同步。

第 2 个问题是:设置默认值后,子组件数据在视图中不更新。

这里我们大胆的猜想,是不是跟数据类型有关?

引用数据类型不更新,基本数据类型会更新。

验证: 子组件引用数据类型不更新,基本数据类型会更新

父组件:

// 父组件
<template>
  <div>
    <div class="father-box">
      <!-- 这里获取不到子组件的值 -->
      <p>我是父页面-此时:son3 组件的值{{ age }} </p>
    </div>
    <!-- 没有给子组件传递值 -->
    <son3></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const age = ref()
</script>

子组件:

// 子组件
<template>
  <div class="son-box">
    <h1 >我是 son3 组件</h1>
    <h2>{{  ageValue }}</h2>
    <button @click="changeHandler">改变值</button>
  </div>
</template>
<script setup lang="ts">
import { defineModel } from "vue";
// 这次是一个基本数据类型
let ageValue = defineModel('age', {
  // 初始渲染的时候会显示默认值
  default: 10,
  type: Number,
});
// 改变值,页面会更新吗?
const changeHandler = ()=>{
  console.log(ageValue)
  ageValue.value = 100
}
</script>

子组件引用数据类型不更新,基本数据类型会更新

总结: defineModel 使用默认值会造成 2 个影响

defineModel 使用默认值后:

  1. 导致父组件与子组件之间的数据不同步;
  2. 如何默认值是应用数据类型,子组件数据在视图中不更新;
  3. 如果默认值是基本数据类型,子组件数据在视图中会更新。

ps: 尽量不要在 defineModel 中使用默认值。

处理 v-model 修饰符

我们知道了 v-model 有一些内置的修饰符。例如.trim, .number, .lazy

有些时候,我们想自定义修饰符,如:将 v-model 绑定输入的字符串值第一个字母转为大写。

我们可以使用 defineModel 的 get 和 set 选项。

v-model 修饰符实现:第一个字母转为大写

父组件:

// 父组件
<template>
  <div>
    <div class="father-box">
      <!-- 这里获取不到子组件的值 -->
      <p>我是父页面-此时:child 组件的值{{ surName }} </p>
    </div>
    <!-- 修饰符 titleCase 在 defineModel 解构的第 2 个参数中可以拿到 -->
    <child  v-model.titleCase="surName"></child>
  </div>
</template>
<script setup lang="ts">
import child from '@/components/child.vue'
import { ref } from 'vue'
const surName = ref()
</script>
<style>
.father-box{
  background: palegreen;
}
</style>

子组件:

// 子组件
<template>
  <input type="text" v-model="modelValue" />
</template>
<script setup>
import { defineModel } from 'vue'
// 解构
const [modelValue, modifiers] = defineModel({
  set(value) {
    // 正则表达输入的是否是 26 个英文
    const regex = /^[a-zA-Z]+$/
    if(regex.test(value) && modifiers.titleCase){
      return value.charAt(0).toUpperCase() + value.slice(1)
    } else {
      // 如果不符合要求返回空字符
      return value
    }
  }
})
console.log(modelValue)
console.log(modifiers)
</script>
<style>
.son-box{
   background: pink;
}
</style>

v-model 修饰符实现:第一个字母转为大写

v-bind 的简写语法

// 以前的
<img  :src="src" :alt="alt">
//3.4 版本可以简写为
<img  :src :alt>

v-bind 的简写语法用在组件传递值上

<template>
  <div>
    // 简写语法
    <son3 :userName :age></son3>
    <!-- 等价与以前这样写 -->
    <!-- <son3 :userName="userName" :age="age"></son3> -->
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
import { ref } from 'vue'
const userName = ref('李四')
const age = ref(18)
</script>

v-bind 的简写语法用在组件传递值上

更高效的反应性系统 watchEffect

<template>
  <div>
    <h1>分数{{ fraction }}</h1>
    <button @click="onChangeFraction">努力学习,改变分数</button>
  </div>
</template>
<script setup lang="ts">
import { ref ,watchEffect} from 'vue'
const fraction = ref(540)
// 现在 fraction 的值不发生变化
const onChangeFraction = () => {
  fraction.value = 540
}
// 不会触发 watchEffect 的回调
watchEffect(() => console.log(fraction.value))
</script>

在 vue3.4 之前,即使计算结果(fraction)保持不变,每次 fraction.value 都将触发 watchEffect 的回调。

而现在只要 fraction 的值不变化,不会触发 watchEffect 的回调。

更高效的反应性系统 watchEffect

编译器性能优化

解析速度提高一倍。

解析器从头开始重写,速度快了一倍。

与旧模板相比,它在一半的时间内解析相同的模板。

旧的解析器是一个递归下降解析器,它使用大量正则表达式和低效的前瞻搜索。

新的解析器使用 htmlparser2。

它以线性方式迭代输入,具有最少的前瞻和回溯。

并在很大程度上消除了对正则表达式的依赖。

删除了已弃用的功能

1.全局 JSX 命名空间

从 3.4 开始,Vue 默认不再注册全局 JSX 命名空间。

这是避免与 React 发生全局命名空间冲突,以便两个库的 TSX 可以共存于同一个项目中。

这应该不会影响使用最新版本的 Volar 的 SFC 的用户。

如果您使用的是 TSX,则有两种选择:

第 1 种:在升级到 3.4 之前,在 tsconfig.json 中将 jsxImportSource 显式设置为’vue’。您还可以通过在文件顶部添加/* jsxImportSource vue*/注释来选择加入每个文件。

第 2 种:如果您的代码依赖于全局 JSX 命名空间的存在,例如使用 JSX.Element 类型等,则可以通过显式引用 vue/jsx 来保留 3.4 之前的确切全局行为,这将注册全局 JSX 命名空间。

2.其他已删除的功能

  1. 反应性转换在 3.3 中被标记为不推荐使用,现在在 3.4 中被删除。
  2. app.config.unwrapInjectedRef已被删除。
  3. @vnode-xx模板中的事件侦听器现在是编译器错误,而不是弃用警告。请改用@vue:XXX侦听器。
  4. v-is指令已被删除。它在 3.3 中已弃用。请改用带vue:前缀的is属性。

监听子组件的生命周期:@vnode-xx 更改为@vue:xxx

在已经删除的功能中,第 2 点:@vnode-xx更改为@vue:xxx

有些时候,我们需要去监听子组件的生命周期。

有 2 种办法:

  1. 在子组件的各个生命周期中使用 emit 抛出方法,然后父组件调用
    缺点:第 3 方组件必须如果没有提供 emit 的话,我们就可以使用下面这一种
  2. 第 2 种: 在组件中使用@vnode-xx(3.4 之前)
    现在@vnode-xx更改为@vue:xxx

监听子组件的生命(vue3.4 之前)

<template>
  <div>
    <!-- 在 vue3.4 之前监听子组件的生命周期可以使用 @vnode-mounted="fn" -->
    <son3 @vnode-mounted="sonMounted"></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const sonMounted = () =>{
  console.log('组件 dom 渲染完成')
}
</script>

监听子组件的生命(vue3.4)

<template>
  <div>
    <!-- 现在使用@vue:mounted="fn" @vue:后面是生命周期-->
    <son3 @vue:mounted="mountedDoThing" ></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const mountedDoThing = () =>{
  console.log('子组件的挂载阶段完成')
}
</script>

监听子组件的生命

监听子组件的生命-奇怪的地方

细心的小伙伴刚刚可能发现了,<son3 @vue:mounted="mountedDoThing" ></son3>@vue:后面的生命周期是原来 vue2 的mounted

为啥不使用 vue3 的onMounted呢?

因为:如果使用的是onMounted的话,将无法监听子组件(son3)是否在页面中被挂载了。

这里大家是否会觉得奇怪?

我也想了很久,也没有找到问题原因。机智的小伙伴可以帮我解惑一下~

下面我们看下使用 vue3 的生命周期是否会触发。

奇怪的地方:如果使用 vue3 的生命周期将不会被触发

<template>
  <div>
    <!-- 这里是 vue3 的 onMounted 生命周期,
      onMountedDoThing 函数将不会被触发
        如果使用的是 mounted 将会被触发  -->
    <son3  @vue:onMounted="onMountedDoThing"></son3>
  </div>
</template>
<script setup lang="ts">
import son3 from '@/components/son3.vue'
const onMountedDoThing = () => {
  console.log('onMounted 不会被触发')
}
</script>

如果使用 vue3 的生命周期将不会被触发

v-is 指令已被删除,改用带 vue:前缀的 is 属性

在 vue3.4 中,v-is 指令已被删除,它在 3.3 中已弃用,请改用is="vue:想替换成的标签"

有些时候,我们想替换某个原生元素,这个时候我们就可以 is 来实现。

下面我们将 tr 标签和 p 标签替换成 li 标签:

<template>
  <div>
    <ul>
      <tr is="vue:li">tr 变成 li 标签</tr>
      <p is="vue:li">p 变成 li 标签</p>
    </ul>
  </div>
</template>

将 tr 标签和 p 标签替换成 li 标签

结语

随着 Vue 3.4 带来的这些更新和改进,我们可以看到 Vue 框架在为开发者提供更高效、更强大的开发体验方面迈出的坚实一步。从性能提升到 API 的丰富,再到对 TypeScript 的优化,每一项更新都体现了 Vue 团队对技术前沿的追求和对社区反馈的重视。

如果文中有什么错误,欢迎大家评论指出,以免更多的人被误导,感激不尽。

「点点赞赏,手留余香」

1

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系maynote@foxmail.com处理
码云笔记 » vue3.4新特性有哪些,通过案例让你看的明明白白

发表回复