vue3.4的更新,保证你看的明明白白

defineModel 同学已经转正

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

defineModel的简单介绍

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

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

使用 $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>

使用 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 进行数据的双向绑定更加友好。
比原来更加方便了。爽歪歪!
原来需要再一个合适的时机(事件触发)写上:
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 设置默认值

// 父页面
<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>


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

我们通过给defineModel设置了默认值。
子组件也正确显示了默认值,但是父页面获取不到子组件的值。
这导致导致父组件与子组件之间的数据不同步。这个是我们发现的第1个问题
第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.如何默认值是应用数据类型,子组件数据在视图中不更新,
  如果默认值是基本数据类型,子组件数据在视图中会更新。
ps: 尽量不要在defineModel中使用默认值。

处理 v-model 修饰符

我们知道了 v-model 有一些内置的修饰符。
例如 .trim, .number, .lazy
有些时候,我们想自定义修饰符。
如:将v-model 绑定输入的字符串值第一个字母转为大写。
我们可以使用 defineModel 的 getset 选项。

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-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>

更高效的反应性系统 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的回调

编译器性能优化

解析速度提高一倍
解析器从头开始重写,速度快了一倍。
与旧模板相比,它在一半的时间内解析相同的模板。
旧的解析器是一个递归下降解析器,它使用大量正则表达式和低效的前瞻搜索。
新的解析器使用 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种: 在组件中使用@vnode-xx(3.4之前)
现在@vnode--更改为@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>

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>

Vue 3.4 发布地址

https://blog.vuejs.org/posts/vue-3-4#removed-deprecated-features

尾声

如果你觉得我写的不错的话,
请给我点一个推荐,
周末都在写这个,有能力可以给我打赏(手动狗头)
最近想吃亲嘴烧,最好可以喝一瓶水,因为辣条有点辣(手动狗头)
posted @   南风晚来晚相识  阅读(2009)  评论(6编辑  收藏  举报
相关博文:
阅读排行:
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
历史上的今天:
2022-06-25 vue3封装搜索表单组件
2022-06-25 Ant Design Vue栅格Grid的使用
2022-06-25 React中setState方法详细讲解
点击右上角即可分享
微信分享提示