vue3新特性

642 阅读4分钟

一、vue3给的几颗糖

1. suspense

一个具有插槽的内置组件

<suspense>
    <template #default>
        <main-model></main-model>
    </template>
    <template #fallback>
        <div>loading</div>
    </template>
</suspense>

2.Fragment

在vue2中一个组件只能有一个根节点,但vue3不再受此限制,会自动将多个元素保存在一个虚拟元素fragment中

    <template>
        <h1>一级标题</h1>
        <h2>二级标题</h2>
        <h2>二级标题</h2>
        <h2>二级标题</h2>
    </template>

3.teleport

传送门——类似于ElementUI里dialog组件的appendTo属性,能将组件的html结构转移到指定的容器元素中,通常用于dialog的情况。

<template>
    <div @click="showDialog = true">打开弹窗</div>
    <teleport to="body">
        <div class="mask" v-show="showDialog">
            <div class="dialog">
                <h3>title</h3>
                <button @click="showDialog = false">关闭弹窗</button>
            </div>
        </div>
    </teleport>
</template>

二、Composition API

如果按照vue2的option配置方法写组件,当业务复杂度越来越高,代码量会不断加大;由于相关业务的代码需要遵循option的配置写到特定的区域,导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的

image.png

可以先来看看几个基础的API:

1. setup

  • 返回对象时,对象中的属性方法都可以在模版中直接使用
  • 返回渲染函数,则可以自定义渲染内容
setup(){
    // 数据
    let name = "suosuojiang"
    let age = "18"
    // 方法
    function sayHello(){
        alert(`我叫${name},我${age}岁了,你好啊`)
    }
    function test2(){
        console.log(this.sex)
        console.log(this.sayWelcom)
    }
    // 返回一个对象
    return {
        name,
        age,
        sayHello,
        test2
    }
    //返回一个函数(渲染函数)
    // return () => h('h1','ssj')
}

2. ref:定义响应式数据

  • 接收的数据可以是基本类型,也可以是对象类型
  • 基本类型:响应式仍然是靠Object.defineProperty()的get和set完成
  • 对象类型:内部求助了Vue3.0中的一个新函数————reactive函数
import {ref} from 'vue'

<script>
    setup(){
        let name = ref("suosuojiang") //
        function changeInfo(){
            name.value = "ssj"
        }
        return {
            name,
            changeInfo
        }
    }
</script>

3.reactive

  • 作用是定义对象类型的响应式数据(基本类型不要用它,要用ref)
  • 返回一个代理对象proxy
  • reactive定义的响应式数据是深层次的

image.png

4.watch与watchEffect

  • watch与vue2的配置基本相同,但有坑:
  • 监听reactive定义的响应式数据时,deep配置无效,无法正确获取oldValue的值
  • 监视的是reactive所定义的对象中的某个属性时,deep配置有效
  • watchEffect不用指定要监听的对象,回调里用到谁就监听谁
// 情况一:监视ref所定义的响应式数据
watch(sum, (newV, oldV)=>{
    console.log('sum值变化了',newV, oldV)
},{immediate: true})

//情况二:监视ref所定义的多个响应式数据
watch([sum,msg], (newV, oldV)=>{
    //此时newV和oldV也变成了数组
    console.log('msg值变化了',newV, oldV)
})

/*
    情况三:监视reactive定义的响应式数据的全部属性
    1.注意:此处无法正确获得oldV
    2.注意:强制开启深度监视,deep配置无效
*/
watch(person,(newV, oldV)=>{
    console.log('person值变化了',newV, oldV)
},{deep:false})//deep配置无效

//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=> person.age,(newV, oldV)=>{
    console.log('person的age变化了',newV, oldV)
})

// 情况五:监视reactive定义的响应式数据中的多个属性
watch([()=> person.name,()=> person.age],(newV, oldV)=>{
    console.log('person的name和age变化了',newV, oldV)
})

// 特殊情况
watch(()=> person.jobs,(newV, oldV)=>{
    console.log('person的job变化了',newV, oldV)
},{deep:true})//此处由于监视的是reactive所定义的对象中的某个属性,deep配置有效

watchEffect(()=>{
    const x1 = sum.value
    console.log('是谁的大眼睛,还没看到我堂堂watchEffect的会调?')
})

5.computed

两种写法:

// 计算属性--简写 没有考虑计算属性被修改的情况
person.fullName = computed(()=>{
    return person.firstName + '-' + person.lastName
})

// 计算属性--完整写法
let fullName = computed({
    get(){
        return person.firstName + '-' + person.lastName
    },
    set(value){
    const nameArr = value.split('-')
    person.firstName = nameArr[0]
    person.lastName = nameArr[1]
    }
})

6.生命周期

image.png

三、性能优化:又快又小

vue3在性能方面比vue2快了近2倍,打包后的体积小1/3到1/2.

1. 优化diff算法:

vue2中的虚拟dom是进行的全量对比

    <template>
        <h1>一级标题</h1>
        <h2>{{title}}</h2>
        <h2>二级标题</h2>
        <h2>二级标题</h2>
    </template>

vue3中则新增了PatchFlag,只对比带有PatchFlag的节点,通过当前PatchFlag标记的信息来确定当前节点要比对的具体内容。

补充:PatchFlag核心思想:会根据vnodepatchFlag上具有的属性来执行不同的patch方法,如果没有patchFlag那么就执行full diff,也就是这里的patchProps,那么猜也知道,patchProps应该包含了下面大部分的单个patch,即:

  • patchClass
  • patchStyle
  • patchEvent
  • 等等

举例:

<template>
  <div :class="classNames" :id="id">{{name}}</div>
</template>

编译后:

createElement(
  "div",
  { class: classNames, id: id },
  [name],
  PatchFlags.TEXT | PatchFlags.CLASS | PatchFlags.PROPS,
  ["id"] // 标明具体那几个props是动态的
)

2. vue2响应式原理依靠Object.defineProperty对数据的劫持;vue3则通过Proxy拦截对象中任意属性的变化,包括属性的读写、增删,另外还通过Reflect对被代理对象(源对象)的属性进行操作

3. tree-shaking

核心api都支持tree-shaking,通过包引入的方式而不是直接在实例化时就注入,只会对使用到的功能或特性进行打包(按需打包),故而体积更小。

四、其他

1. 自定义hooks

import { onMounted, onUnmounted, ref } from "vue";

function useWindowResize() {
  const width = ref(0);
  const height = ref(0);

  function onResize() {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  }

  onMounted(() => {
    window.addEventListener("resize", onResize);
    onResize();
  });

  onUnmounted(() => {
    window.removeEventListener("resize", onResize);
  });

  return {
    width,
    height
  };
}

export default useWindowResize;
<template>
  <div id="app">
    <h1>{{ count }}</h1>
    <button @click="plus">plus</button>
    <h1>屏幕尺寸:</h1>
    <div>宽度:{{ width }}</div>
    <div>高度:{{ height }}</div>
  </div>
</template>

<script lang="ts">
import { ref, watch } from "vue";
import useWindowResize from "./hooks/useWindowResize";

export default {
  name: "App",
  setup() {
    const count = ref(0);
    function plus() {
      count.value++;
    }
    watch(count, () => {
      document.title = "update: " + count.value;
    });

    const { width, height } = useWindowResize();

    return {
      count,
      plus,
      width,
      height
    };
  }
};
</script>

2. Better TypeScript Support