ETJava Beta | Java    注册   登录
  • 搜索:
  • vue3.4的更新,保证你看的明明白白

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/IwishIcould/p/18263906
    如有侵权 请联系我们删除  (页面底部联系我们)  

    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

    尾声

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