加载页面中...
Vue–04Vue3 | lwstkhyl

Vue--04Vue3

来自b站课程尚硅谷Vue2.0+Vue3.0全套教程

写在前面:此笔记来自b站课程尚硅谷Vue2.0+Vue3.0全套教程 / 资料下载 / 我的练习文件(教程中的练习)

简介

比Vue2的优势

  • 性能提升:打包大小更小、渲染更快、内存占用更少

  • 源码升级:使用Proxy代替defineProperty实现响应式,重写虚拟DOM的实现和Tree-Shaking

  • 更好地支持TS

  • 一些新的特性


两种创建方式:vue-cli和vite

vue-cli:同Vue2的创建方式,选择Vue3即可

vite:新一代前端构建工具

  • 优势:传统方法都是先把各个模块打包完成后再启动,vite是先启动服务器,再根据HTTP请求申请模块

    • 开发环境中,无需打包操作,可快速的冷启动

    • 轻量快速的热重载(HMR)

    • 真正的按需编译,不再等待整个应用编译完成

  • 方法:

    • 创建工程:npm init vite-app <project-name>

    • 进入工程目录:cd <project-name>

    • 安装依赖:npm install

    • 运行:npm run dev


工程结构

  • src/main.js

    import { createApp } from 'vue' //引入createApp工厂函数,而不是Vue构造函数
    import App from './App.vue'
    const app = createApp(App) //创建应用实例对象app,类似于vm,但更“轻”
    app.mount('#app') //挂载
    

    app.unmount('#app')卸载,调用后页面上不显示内容

  • src/App.vue:唯一区别是组件模板结构可以没有根标签

    <template>
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
    </template>
    
  • 其它地方没有变化


安装开发者工具:搜索Vue.js devtools,edge上的同时支持Vue2/3,chrome上的找到logo右下角有beta标志的

常用Composition api

Composition api:组合式API

setup

是Vue3中一个新的配置项,值为一个函数

  • 组件中所有的数据、方法等均要配置在setup中,是所有组合式API“表演的舞台”

  • 两种返回值:

    • (常用)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用

    • (了解)若返回一个渲染函数,则可以自定义渲染内容

  • 注意:

    • 尽量不要与Vue2配置混用

      • Vue2配置(data/methos/computed…)中可以使用this.属性访问到setup中的属性、方法,但setup不能访问到Vue2配置

      • 如果有重名,setup优先

    • setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性(如果需要返回一个Promise,需要suspense和异步组件的配合)

返回一个对象(常用):

<template>
  <p>name:</p>
  <p>age:</p>
  <button @click="sayhello">sayhello</button>
</template>
<script>
export default {
  name: 'App',
  setup() { //此处仅作演示,未考虑响应式
    let name = 'abc';
    let age = 18;
    function sayhello(){
      alert(`name:${name}, age:${age}`);
    }
    return {name, age, sayhello};
  }
}
</script>

组合式API1

返回一个渲染函数:就是Vue2中的render函数

<script>
import {h} from 'vue'
export default {
  name: 'App',
  setup() {
    return ()=>h('h1', '返回一个渲染函数');
  }
}
</script>

此时页面上只有<h1>返回一个渲染函数</h1>

ref函数

不是写在标签中的ref属性,ref属性仍然可用

在setup函数中,如果只是用let a=1的方式声明,则不会响应式,需要用ref函数处理

import {ref} from 'vue'
export default {
  setup() {
    let 属性名 = ref(属性值); //声明
    function 方法名(){
      属性名.value = 新属性值 //修改属性
    }
    return {属性名};
  }
}
  • 在标签中仍使用``读取属性值,不用加.value

  • 属性值也可以是一个对象,在setup中仍使用对象.value.属性名

<template>
  <p>name:</p>
  <p>age:</p>
  <p>job: </p>
  <button @click="change">change</button>
</template>
<script>
import {ref} from 'vue'
export default {
  name: 'App',
  setup() {
    let name = ref('abc');
    let age = ref(18);
    let job = ref({
      name:'ABC',
      salary: 10000
    });
    function change(){
      age.value ++;
      job.value.salary = job.value.salary * 2 ;
    }
    return {name, age, job, change};
  }
}
</script>

如果ref函数中传入的是一个基本类型的数据,则返回一个RefImpl(reference implement)实例对象(引用实现的实例对象,简称为“引用对象”/“ref对象”)

组合式API2

ref也是用Object.defineProperty的getter/setter实现的响应式,所以value也是(...)的形式

组合式API3

引用对象的原型对象就类似于vm._data,为了更方便的取出value,把value在引用对象中又放了一份


如果ref函数中传入的是一个对象类型的数据,则不会遍历各属性,让各属性也变成引用对象,而是把对象数据变成Proxy的实例对象(ES6的新语法,称为“代理对象”)

reactive函数

定义一个对象类型的响应式数据,使用方法类似ref函数,都是先引入再const 代理对象 = reactive(源对象)。内部基于Proxy实现,通过代理对象操作源对象内部数据进行操作。ref函数能处理对象类型数据,将其转为代理对象,也是调用了reactive函数

注意

  • 在setup调用数据时,可以直接对象.属性名,而不用对象.value.属性名

  • 如果想要把一个数组也变成响应式,想监测到数组中某个元素的改变,也可以用reactive函数,而Vue2中不行

  • reactive定义的响应式数据是“深层次的”

<template>
  <p>name:</p>
  <p>age:</p>
  <p>job: </p>
  <p>hobby:</p>
  <button @click="change">change</button>
</template>
<script>
import {reactive} from 'vue'
export default {
  name: 'App',
  setup() {
    let person = reactive({
      name: 'abc',
      age: 18,
      job: {
        name:'ABC',
        salary: 10000
      },
      hobby: ['学习', '吃饭', '打游戏']
    });
    function change(){
      person.age ++;
      person.job.salary = person.job.salary * 2 ;
      person.hobby[2] = '听歌'
    }
    return {person, change};
  }
}
</script>

总结:reactive和ref的对比

  • 定义数据

    • ref:基本类型数据

    • reactive:对象(或数组)类型数据

    注:ref也可以用来定义对象(或数组)类型数据,它内部会自动调用reactive转为代理对象

  • 原理

    • ref:通过Object.defineProperty()getset实现响应式(数据劫持)

    • reactive:通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。

  • 使用

    • ref:操作数据需要.value,读取数据时模板中直接读取不需要.value

    • reactive:操作数据与读取数据均不需要.value

    通常都用reactive对数据进行处理,如果有多个基本数据类型的数据,则封装到一个对象中

setup的两个注意点
  • setup执行的时机:在beforeCreate之前执行一次,此时this是undefined

  • setup的参数

    • props:值为对象,包含组件外部传递过来,且组件内部声明接收了的属性

    • context:上下文对象

      • attrs:值为对象,包含组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs

      • slots:收到的插槽内容,相当于this.$slots

      • emit:分发自定义事件的函数,相当于this.$emit

<!-- App.vue -->
<template>
  <hello-world msg="HelloWorld" school="ABC" @hello="showMsg">
    <template v-slot:test1> <!-- Vue3中尽量使用v-slot -->
      <span>我是要插入的标签1</span>
    </template>
    <template v-slot:test2>
      <span>我是要插入的标签2</span>
    </template>
  </hello-world>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
  name: 'App',
  components: {HelloWorld},
  setup() {
    function showMsg(value){
      alert('HelloWorld');
      console.log(value); 
    }
    return {showMsg};
  }
}
</script>
<!-- HelloWorld.vue -->
<template>
  <p>name: </p>
  <p>age: </p>
  <p>msg: </p>
  <button @click="emitHello">触发helloworld组件的hello事件</button>
</template>
<script>
import {reactive} from 'vue'
export default {
  name: 'HelloWorld',
  props: ['msg', ], //只接收msg
  emits: ['hello', ], //声明接收自定义事件(Vue3新增配置项),新版本中已不需要该写法,旧版本中不写会有警告
  setup(props, context) {
    let person = reactive({
      name: 'abc',
      age: 18
    });
    console.log("props:", props); 
    console.log("attrs:", context.attrs);
    function emitHello(){
      context.emit('hello', '要携带的数据');
    }
    console.log("slots:", context.slots);
    return {person, emitHello};
  }
}
</script>

组合式API4

Vue3的响应式原理

Vue2的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

    Object.defineProperty(data, 'count', {
        get () {}, 
        set () {}
    })
    
  • 存在问题:

    • 直接新增/删除对象数据中的属性,界面不会更新(新增属性必须调用this.$set/Vue.set方法,删除属性必须调用this.$delete/Vue.delete方法)

    • 直接通过下标修改数组,界面不会自动更新(必须调用this.$set/Vue.set方法,或者用splice这种数组身上的变更方法)


Vue3的响应式

  • 实现原理:

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括属性值的读写属性的添加属性的删除

    • 通过Reflect(反射):对源对象的属性进行操作

    相比于直接用Object.defineProperty这些基本方法,Reflect系列方法执行时返回bool值,标明语句是否成功执行,而不是直接报错,这样能让框架的健壮性更强(不容易因报错而中断)

    new Proxy(data, { //当person上的任一属性改变时就触发
      //读取属性值时调用
      get (target, prop) { //target是原对象data,prop是改变的属性名
        return Reflect.get(target, prop)
      },
      //修改属性值或添加新属性时调用
      set (target, prop, value) { //value是新属性值
        return Reflect.set(target, prop, value)
      },
      //删除属性时调用
      deleteProperty (target, prop) {
        return Reflect.deleteProperty(target, prop)
      }
    })
    

Vue2响应式

let p2 = {};
Object.defineProperty(p, 'name', {
    configurable: true, //使p中的属性能被删除
    get(){
        return person.name;
    },
    set(value){
        person.name = value;
    }
});

Vue3响应式

const p3 = new Proxy(person, {
    get(target, prop){
        // return target[prop]; //注意不能是target.prop,因为prop是字符串
        return Reflect.get(target, prop);
    },
    set(target, prop, value){
        // target[prop] = value;
        return Reflect.set(target, prop, value);
    },
    deleteProperty (target, prop) {
        // return delete target[prop]
        return Reflect.deleteProperty(target, prop);
    }
});

注:直接写const p3 = new Proxy(person, {})也能实现p3和person同步增删改,但因为要实现响应式,必须在增删改时做其它处理,所以要重写以上三种操作

computed属性

与Vue2中computed配置方法、功能类似

import {computed} from 'vue'
setup(){
  //简写
  let 计算属性名 = computed(()=>{
    return 
  })
  //完整
  let 计算属性名 = computed({
      get(){
          return 
      },
      set(value){
          const nameArr = value.split('-')
          person.firstName = nameArr[0]
          person.lastName = nameArr[1]
      }
  })
}

<template>
  姓:<input type="text" v-model="person.firstName">
  名:<input type="text" v-model="person.lastName">
  <p>全名:</p>
  全名:<input type="text" v-model="person.fullNameChange">
</template>
<script>
import { reactive, computed } from 'vue';
export default {
  name: 'App',
  setup() {
    let person = reactive({
      firstName: '',
      lastName: '',
    });
    //简写
    person.fullName = computed(()=>{
      return person.firstName + '·' + person.lastName
    });
    //完整
    person.fullNameChange = computed({
      get(){
        return person.firstName + '·' + person.lastName
      },
      set(value){ //修改计算属性
        const nameArr = value.split('·');
        person.firstName = nameArr[0];
        person.lastName = nameArr[1];
      }
    });
    return {person};
  }
}
</script>

watch属性和watchEffect函数

与Vue2中watch配置方法、功能类似

import {watch} from 'vue'
setup(){
  //1. 监视ref定义的一个响应式数据
  watch(要监视的属性变量, (newVal, oldVal)=>{
    //newVal/oldVal是新值/旧值
  }, {
    immediate: true //页面初始化时执行一次,下同
  })
  //2. 监视ref定义的多个响应式数据
  watch([变量1, 变量2, ...], (newVal, oldVal)=>{
    //newVal/oldVal是数组形式,对应[变量1, 变量2, ...]
  })
  //3. 监视reactive定义的响应式数据
  watch(要监视的属性变量, (newVal, oldVal)=>{
    //注意:此处的oldVal与newVal值相同,不能正确获取oldVal
  }, {
    immediate: true,
    deep: false //此处无法配置deep为false,深度监视会被强制开启
  })
  //4. 监视reactive定义的响应式数据中的一个属性
  watch(()=>obj.属性名, (newVal, oldVal)=>{})
  //5. 监视reactive定义的响应式数据中的多个属性
  watch([()=>obj.属性名1, ()=>obj.属性名2, ...], (newVal, oldVal)=>{})
  //特殊情况,当要监视的属性值为一个对象时,不会强制深度监视,若要开启深度监视,需手动配置deep:true
}

总结:reactive的watch

  • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)

  • 监视reactive定义的响应式数据中某个属性(属性值为对象)时:deep配置有效,且默认为false

补充:ref定义的数据的value

  • 如果ref定义的是基本数据类型,watch时就不写.value

  • 如果是对象,直接写watch(obj, (n, o)=>{}),会监视不到obj属性的改变。解决办法:

    • 加上.valuewatch(obj.value, (n, o)=>{})

    • 加上deep配置项:watch(obj, (n, o)=>{}, {deep: true})


Vue3新增了watchEffect函数

  • watch:既要指明监视的属性,也要指明监视的回调

  • watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性

import {watchEffect} from 'vue'
setup(){
  watchEffect(()=>{
    //回调中用到的数据只要发生变化,则直接重新执行回调
    const x1 = sum.value
    const x2 = person.age
    console.log('watchEffect配置的回调执行了')
  })
}

有点类似computed,都是用到的数据改变时执行

  • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值

  • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

生命周期

生命周期1

  • Vue3中可以继续使用Vue2中的生命周期钩子,但有有两个被更名:

    • beforeDestroy改名为 beforeUnmount

    • destroyed改名为 unmounted

  • Vue3的生命周期钩子写法可以与2相同(配置项写法),但也提供了Composition API形式的生命周期钩子,即把函数写到setup中:

    • beforeCreatecreated没有组合式API形式,因为只要把它们的函数体直接写到setup的最前面就行了

    • beforeMount->onBeforeMount

    • mounted->onMounted

    • beforeUpdate->onBeforeUpdate

    • updated->onUpdated

    • beforeUnmount->onBeforeUnmount

    • unmounted->onUnmounted

    import { onBeforeMount, } from 'vue'; //其它钩子写法相同
    export default {
      setup() {
        onBeforeMount(()=>{
          //要执行的语句
        })
      }
    }
    

注意:如果某个钩子既有组合式API写法,又有配置项写法,则写执行组合式API写法中的语句

自定义hook函数

本质是一个函数,把setup函数中使用的组合式API进行了封装

  • 类似于vue2中的mixin

  • 优势: 复用代码,让setup中的逻辑更清楚易懂

通常在src文件夹下新建一个hooks文件夹,里面新建一些以use开头的js文件

/* src/hooks/usexxx.js */
import {reactive, ...} from 'vue'
export default function(){
  //里面可以定义数据、函数、生命周期钩子
  return 数据;
}
/* 组件.vue */
import usexxx from '../hooks/usexxx'
setup(){
  let 数据 = usexxx();
  return 数据
}

例:当鼠标点击页面时,在页面上显示鼠标坐标

/* src/hooks/usePoint.js */
import { reactive, onMounted, onBeforeUnmount } from 'vue';
export default function () {
    let point = reactive({
        x: 0,
        y: 0
    });
    function savePoint(e) {
        point.x = e.pageX;
        point.y = e.pageY;
    }
    onMounted(() => {
        window.addEventListener('click', savePoint);
    });
    onBeforeUnmount(() => {
        window.removeEventListener('click', savePoint);
    })
    return point;
}
<!-- App.vue -->
<template>
  <p>当前鼠标坐标为(, )</p>
</template>
<script>
import usePoint from './hoooks/usePoint'
export default {
  name: 'App',
  setup() {
    let point = usePoint();
    return {point};
  },
}
</script>

toRef函数

创建一个ref对象,其value值指向另一个对象中的某个属性

  • 应用:要将响应式对象中的某个属性单独提供给外部使用时,同时不丢失响应式
import {reactive, toRef} from 'vue'
setup(){
  let data = reactive({...});
  const 数据名1 = toRef(data, 属性名);
  //此时`数据名1`就是Ref对象,指向`data.属性名`
  const 数据名2 = toRef(data.子对象, 属性名);
}

注:toRef(data, 属性名)不能写成ref(data.属性名),如果这样写,相当于复制了另一个data.属性名(即创建出一个新ref对象,值为data.属性名),此时修改它,data不会发生改变

<template>
  <p>name:</p>
  <p>age:</p>
  <p>salary:</p>
  <button @click="age++">changeAge</button>
  <button @click="salary*=2">changeSalary</button>
</template>
<script>
import {reactive, toRef} from 'vue'
export default {
  name: 'App',
  setup() {
    let person = reactive({
      name: 'abc',
      age: 18,
      job: {
        salary: 10000
      },
    });
    return {
      name: toRef(person, 'name'),
      age: toRef(person, 'age'),
      salary: toRef(person.job, 'salary'),
    };
  }
}
</script>

补充:toRefstoRef功能一致,但可以批量创建多个ref对象

import {reactive, toRefs} from 'vue'
setup(){
  let data = reactive({...});
  return {
    ...toRefs(data)
  }
}

<template>
  <p>name:</p>
  <p>age:</p>
  <p>salary:</p>
  <button @click="age++">changeAge</button>
  <button @click="job.salary*=2">changeSalary</button>
</template>
<script>
import {reactive, toRefs} from 'vue'
export default {
  name: 'App',
  setup() {
    let person = reactive({
      name: 'abc',
      age: 18,
      job: {
        salary: 10000
      },
    });
    return {...toRefs(person),};
  }
}
</script>

其它Composition api

shallowReactive和shallowRef

使用方法同ref函数reactive函数

  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)

什么时候使用:

  • shallowRef:如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换原数据

  • shallowReactive:如果有一个对象数据,结构比较深,但变化时只是外层属性变化

readonly和shallowReadonly

当我们不希望某个数据被修改时使用,使用方法同ref函数,可以用在使用ref或reactive修饰的数据/普通数据上

  • readonly:让一个响应式数据变为只读的(深只读)

  • shallowReadonly:让一个响应式数据变为只读的(浅只读)

为什么不用“直接不将数据用ref/reactive修饰”的方法实现只读数据:这样数据仍可被修改,只是因为没有响应式,在页面上不改变,但readonly函数可以让数据从根本上不能被修改

还有一种情况:数据不是定义在自己的组件中,而是定义在别的组件中,只有在自己的组件中才想让其只读

toRaw和markRaw

使用方法同readonly和shallowReadonly

  • toRaw:将一个reactive生成的响应式对象转为普通对象

    • 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
  • markRaw(更常用):标记一个普通对象,使其永远不会再成为响应式对象

    • 有些值不应被设置为响应式的,例如复杂的第三方类库等

    • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

    比如要给一个reactive对象上再追加一个很复杂的对象,这个追加的对象不需要更改,但因为Proxy,新追加的对象默认是响应式的,就需要使用:data.新属性名=markRaw(追加的对象)

customRef

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制

import {customRef} from 'vue'
setup(){
  function 自定义ref函数名(value){
    return customRef((track, trigger)=>{
      return { //类似于计算属性
        get(){ //当data被读取时触发
          track(); //通知Vue追踪该数据的变化
          return value;
        },
        set(newVal){ //当data被修改时触发
          value = newVal;
          trigger(); //通知Vue重新解析模板(再次调用get()函数)
        }
      }
    });
  }
  let data = 自定义ref函数名();
  return {data};
}

例:实现防抖效果,即在输入框中输入文字,下面的标签中延时展示

<template>
  <input type="text" v-model="keyword">
  <h3></h3>
</template>
<script>
import {customRef} from 'vue'
export default {
  name: 'App',
  setup() {
    function myRef(value, delay){
      let timer = null;
      return customRef((track, trigger)=>{
        return {
          get(){
            track();
            return value;
          },
          set(newVal){
            if(timer) clearTimeout(timer);
            timer = setTimeout(()=>{
              value = newVal;
              trigger();
            }, delay);
          }
        }
      });
    }
    let keyword = myRef('', 500);
    return {keyword};
  }
}
</script>

provide与inject

实现祖与后代组件间通信(特别是祖孙组件间):父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据

/* 祖组件 */
import {provide} from 'vue'
setup(){
    provide(数据名, obj)
}
/* 后代组件 */
import {inject} from 'vue'
setup(props, context){
    const obj = inject(数据名)
    return {obj}
}

/* 祖组件 */
import {reactive, provide} from 'vue'
setup(){
  let car = reactive({name:'奔驰',price:'40万'})
  provide('car',car)
}
/* 后代组件 */
import {inject} from 'vue'
setup(props, context){
  const car = inject('car')
  return {car}
}

响应式数据的判断

  • isRef:检查一个值是否为一个ref对象

  • isReactive:检查一个对象是否是由reactive创建的响应式代理

  • isReadonly:检查一个对象是否是由readonly创建的只读代理

  • isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理

import {isProxy, isReactive, isReadonly, isRef, reactive, readonly, ref} from 'vue'
export default {
  name: 'App',
  setup() {
    let obj = reactive({name: 'abc'})
    let a = ref(0)
    let a_readonly = readonly(a)
    console.log(isRef(a));  //true
    console.log(isReactive(obj));  //true
    console.log(isReadonly(a_readonly));  //true
    console.log(isProxy(a_readonly));  //true
  }
}

Composition API的优势

<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cc55165c0e34069a75fe36f8712eb80~tplv-k3u1fbpfcp-watermark.image"style="height:360px"/>

使用传统Options API中,新增或者修改一个需求,就需要分别在data/methods/computed里修改。而Composition API可以把同一功能/变量涉及到的所有函数都写到一起

新的组件

fragment

不需要手动写,是Vue3框架自动生成的,目的是减少标签层级,减小内存占用

  • 在Vue2中:组件必须有一个根标签

  • 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中

teleport

将组件html结构移动到指定位置。比如一个组件中写了一个弹窗,控制弹窗的按钮和弹窗都写在该组件中,此时弹窗的DOM结构会在组件中,而我们想让弹窗在body下显示

<teleport to="移动位置">
  <!-- HTML结构 -->
</teleport>
  • 移动位置处为CSS选择器,例如body/#app

suspense

等待异步组件时渲染一些额外内容,让应用有更好的用户体验

import {defineAsyncComponent} from 'vue'
const 异步组件名 = defineAsyncComponent(()=>import('./components/Child.vue'))
export default{
  components: {异步组件名}
}
  • 这种方法称为异步引入/动态引入,而之前直接import 子组件的方法称为静态引入

  • 静态引入时,如果引入不成功/速度慢,整个页面都会卡住;而动态引入就不需要等子组件引入成功,常用于子组件中数据量很大/异步return时

    // 例如return一个Promise对象
    async setup(){
      let p = new Promise((resolve, reject)=>{
        setTimeout(()=>{
          resolve(0)
        }, 1000)
      })
      return await p
    }
    

动态引入存在的问题:因为子组件可能不会与父组件同时显示,需要一些过渡动画来防止突兀的显示

<Suspense>
  <template v-slot:default> <!-- 真正想展示的内容/子组件 -->
    <Child/>
  </template>
  <template v-slot:fallback> <!-- 加载时展示的内容 -->
    <h3>加载中.....</h3>
  </template>
</Suspense>

其他改变

全局API的转移

Vue2有许多全局API和配置,例如注册全局组件Vue.component、注册全局指令Vue.directive

Vue3将全局的APIVue.xxx调整到应用实app

Vue2全局APIVue Vue3实例APIapp
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue3中没有生产提示
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

data选项应始终被声明为一个函数

过度类名的更改

  • Vue2写法

    .v-enter,
    .v-leave-to {
      opacity: 0;
    }
    .v-leave,
    .v-enter-to {
      opacity: 1;
    }
    
  • Vue3写法

    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
    .v-leave-from,
    .v-enter-to {
      opacity: 1;
    }
    

移除keyCode作为v-on的修饰符,同时也不再支持Vue.config.keyCodes.按键别名

移除v-on.native修饰符

  • 父组件中绑定事件

    <my-component
      v-on:自定义事件名="handleComponentEvent"
    />
    
  • 子组件中声明自定义事件

    export default {
      emits: ['自定义事件名']
    }
    

移除过滤器filter

  • 需要一个自定义语法,打破大括号内表达式是JavaScript的假设

  • 建议用方法调用或计算属性去替换过滤器