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

vue3 · 浏览次数 : 0

小编点评

本文介绍了Vue 3.3中defineModel的功能和使用方法,并对比了Vue 3.2和Vue 3.4中的实现差异。 1. **Vue 3.2中的defineModel**:在Vue 3.2中,defineModel用于实现父组件和子组件之间的双向绑定。通过定义一个ref,可以实现数据的同步更新。子组件可以通过$emit触发父组件的事件来更新数据。 2. **Vue 3.3中的defineModel**:在Vue 3.3中,defineModel仍然是一个实验性功能,但已经可以通过定义props和emits选项来接收和更新数据。此外,还提供了get和set选项来实现v-model修饰符的功能。 3. **Vue 3.4中的defineModel**:在Vue 3.4中,defineModel的实现有所改进,包括支持默认值和数据类型的更新。同时,删除了一些不再推荐使用的功能和模板语法。 4. **defineModel的问题**:文章提到了在使用defineModel时可能遇到的两个问题:父子数据不同步和子组件数据不更新。这些问题可能与数据类型有关,基本数据类型更新正常,而引用数据类型可能不会更新。 5. **v-model修饰符**:文章还讨论了v-model修饰符的使用,以及如何在defineModel中实现类似的功能。 6. **编译器性能优化**:最后,文章提到了Vue 3.4相对于Vue 3.3在编译器和解析器方面的性能优化,包括新的解析器和删除了已弃用的功能。 总的来说,Vue 3.4在defineModel的实现上提供了更多的便利性和性能优化,同时也解决了一些在使用过程中可能遇到的问题。

正文

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

尾声

如果你觉得我写的不错的话,
请给我点一个推荐,
周末都在写这个,有能力可以给我打赏(手动狗头)
最近想吃亲嘴烧,最好可以喝一瓶水,因为辣条有点辣(手动狗头)

与vue3.4的更新,保证你看的明明白白相似的内容:

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

defineModel 同学已经转正 defineModel 在vue3.3中还是一个实验性功能, 但是经过一个学期的努力,该同学已经转正。 defineModel的简单介绍 defineModel() 返回的值是一个 ref。 它可以像其他 ref 一样被访问以及修改。 它能起到在父组件和当前变量

Vue3 中的 v-bind 指令:你不知道的那些工作原理

前言 v-bind指令想必大家都不陌生,并且都知道他支持各种写法,比如、、(vue3.4中引入的新的写法)。这三种写法的作用都是一样的,将title变量绑定到div标签的title属性

Go版RuoYi

RuoYi-Go https://github.com/Kun-GitHub/RuoYi-Go 1. 关于我 个人介绍 2. 介绍 后端用Go写的RuoYi权限管理系统 (功能正在持续实现)后端 Gitee地址 3. 前端 RuoYi-Vue3 官方前端Vue3版 4. Go后端技术栈(持续在对齐项

使用Vue3+elementPlus的Tree组件实现一个拖拽文件夹管理

目录1、前言2、分析3、 实现4、踩坑4.1、拖拽辅助线的坑4.2、数据的坑4.3、限制拖拽4.4、样式调整 1、前言 最近在做一个文件夹管理的功能,要实现一个树状的文件夹面板。里面包含两种元素,文件夹以及文件。交互要求如下: 创建、删除,重命名文件夹和文件 可以拖拽,拖拽文件到文件夹中,或着拖拽文

Vite-Wechat网页聊天室|vite5.x+vue3+pinia+element-plus仿微信客户端

基于Vue3+Pinia+ElementPlus仿微信网页聊天模板Vite5-Vue3-Wechat。 vite-wechat使用最新前端技术vite5+vue3+vue-router@4+pinia+element-plus搭建网页端仿微信界面聊天系统。包含了聊天、通讯录、朋友圈、短视频、我的等功

有点儿神奇,原来vue3的setup语法糖中组件无需注册因为这个

前言 众所周知,在vue2的时候使用一个vue组件要么全局注册,要么局部注册。但是在setup语法糖中直接将组件import导入无需注册就可以使用,你知道这是为什么呢?注:本文中使用的vue版本为3.4.19。 关注公众号:【前端欧阳】,给自己一个进阶vue的机会 看个demo 我们先来看个简单的d

Vue 3 Teleport:掌控渲染的艺术

title: Vue 3 Teleport:掌控渲染的艺术 date: 2024/6/5 updated: 2024/6/5 description: 这篇文章介绍了Vue3框架中的一个创新特性——Teleport,它允许开发者将组件内容投送到文档对象模型(DOM)中的任意位置,即使这个位置在组件的

vue3 封装axios

1添加一个新的 http.js文件 封装axios 引入axios //引入Axios import axios from 'axios' 定义一个根地址 //视你自己的接口地址而定 var root2 = 'http://121.4.63.196:8520/api' 定义个小函数来统一参数格式(可

Vue3快速上手

好久没上传了,闲来无事把囤积已久的笔记给上传上传 1. Vue3简介 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece(n 经历了:4800+次提交、40+个RFC、600+次PR、300+贡献者 官方发版地址:Release v3.0.0 One Piece · vue

【干货】Vue3 组件通信方式详解

毫无疑问,组件通信是Vue中非常重要的技术之一,它的出现能够使我们非常方便的在不同组件之间进行数据的传递,以达到数据交互的效果。所以,学习组件通信技术是非常有必要的,本文将总结Vue中关于组件通信的八种方式,帮助大家在使用Vue的过程中更加得心应手! 如果文中有不对、疑惑的地方,欢迎在评论区留言指正...