Vue3-自学笔记

创建工程两个方法

使用vue-cli创建

需要vue-cli版本在4.5以上,创建的步骤和vue2的过程基本一样,只需要选择适配Vue3的环境即可。
在这里插入图片描述

使用vite创建

  1. 开发环境下,无需打包操作,可以快速冷启动。
  2. 轻量快速的热重载HMR
  3. 按需编译,不再等待整个应用编译完成。
    以下是基于webpack构建和vite构建图对比

Vue3-自学笔记_第1张图片
创建过程如下
npm init vite-app 工程名创建工程,(这个时候vue3工程文件中没有依赖包)
cd 工程文件 进入工程文件中操作
npm install (or yarn) 安装依赖
npm run dev (or yarn dev) 运行,在package中将serve修改为dev

新版使用vite创建脚手架

npm init vue@latest 使用vite创建一个Vue项目,该命令会自动安装和执行create-vue,vue官方提供的脚手架工具。安装完成后会有步骤提示操作。
Vue3-自学笔记_第2张图片
cd 项目文件名
npm install
npm run dev

对比分析Vue2和Vue3的工程文件区别

这里对比的都是采用vue create 工程名方法创建的脚手架区别。
在Vue3中当创建完成一个脚手架后,可以查看入口文件main.js文件中的默认代码如下。和Vue2区别还是很大的,不再采用render函数去去渲染。
在这里不再引入Vue构造函数并且这里也不能引入import Vue from 'vue,因为这里进行了处理,获取的Vue构造函数是undefined,所以无法进行实例化new操作。
引入一个名为createApp工厂函数,我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

分解步骤

第一步类似Vue2中的new Vue构造函数,创建的app类似vm实例,但是相比于之前的vm,更加轻量化,将Vue2之前的vm身上不用的东西都去除。
const app = createApp(App)
第二步类似Vue2的el,只不过在这里换成了mount,用于渲染容器
app.mount('#app')

/* Vue2生成的代码 */
// const vm = new Vue({
//   render: h => h(App)
// })
// vm.$mount('#app')

通过打印输出发现createApp(App)的结果如下,不仅有mount挂载方法也有unmount卸载方法
Vue3-自学笔记_第3张图片
其次还有一个区别就是在Vue3的组件模板结构template中,不再有根标签的限制了。
Vue3-自学笔记_第4张图片
Vue3中所有的data都是函数式

常用的Composition Api(组合式API)

setup函数

setup函数是Vue3提供的新配置项,值为一个函数。
setup是所有Composition API的平台。
组件中所有用到的数据,方法等,均要配置在setup中。
setup函数的两种返回值

  1. 返回一个对象,返回对象中的所有属性和方法在模板中均可以直接使用。
  2. 返回一个渲染函数,可以自定义渲染到模板中的内容。会替换原有的模板内容。(需要从vue文件中引入h渲染函数)
  setup() {
    let name = "zq"; //直接声明使用,不需要使用如data,methods等关键字
    function getInfo() {
      alert(`个人信息:${name}`); //函数作用域直接使用
    }
    return {
      name,
      getInfo,
    };
  },

以下是setup返回渲染函数,不常使用

import { h } from "vue";
    return () => {
      return h("h1", "我很牛"); //需要将h渲染函数的返回值交给setup函数的返回值
    };

特别注意的,在Vue3中尽量不要写Vue2相关的代码。如果一定要写,也请看下面的代码按照约定去写。
Vue2的配置项data,methods等可以访问到vue3中setup的属性和方法
但在vue3的setup中不能访问到vue2中data,methods等属性和方法

当出现同同名属性的时候,以Vue3为主
当同时写了vue2和vue3的代码的时候,**在不互相交叉使用的时候,**在Vue3中可以正确显示vue2的内容。

  name: "App",
  data() {
    return {
      year: 2023,
    };
  },
  methods: {
    getYear() {
      alert(`今年是${this.year}`);
    },
  },
  setup() {
    let name = "zq";
    function getInfo() {
      alert(`个人信息:${name}`);
    }
    return {
      name,
      getInfo,
    };
  },

Vue3-自学笔记_第5张图片
以下是交叉数据使用的情况

  data() {
    return {
      year: 2023,
    };
  },
  methods: {
    // 交叉使用vue3的数据,可以正常显示页面
    getYear() {
      this.getInfo();
      alert(`今年是${this.year}年,后面是vue3的属性姓名:${this.name}`);
    },
  },
  setup() {
    let name = "zq";
    function getInfo() {
      alert(`个人信息:${name}`);
    }
    /* 在vue3中交叉获取vue2的数据,页面报错,注意this指向 */
    function getVue2() {
      this.getYear();
      alert(`获取vue2的信息:${this.year}`);
    }
    return {
      name,
      getInfo,
      getVue2,
    };
  },

setup函数不能写成一个async函数,是因为async的返回值是加工后的promise,所以的内容都在promise中需要then调用取出,这就会导致模板看不见其中的内容

setup的执行时机

setup函数执行的时机比beforeCreate生命周期函数早,并且this为undefined
在这里插入图片描述

回顾vue2中的props与插槽等。

props:前提创建一个App组件和Demo组件,并且在App组件中使用Demo组件标签并传递数据,但是在Demo组件中没有设置props配置项接收数组,那么这些组件中传递的值怎么查看

组件实例对象身上的$attrs属性

通过打印组件实例对象,发现所有没有设置props接收的属性,最终都会出现在$attrs上(目前没有设置props:["msg","Vue"])。
Vue3-自学笔记_第6张图片
当设置了 props: ["msg"],只接收一个参数,打印如下。没有被配置项props接收的参数,最终都出现在$attars属性上。
Vue3-自学笔记_第7张图片

slot插槽相关—$slots属性

当在组件标签中添加了HTML原元素的时候,但是没有使用插槽占位。那么那些HTML元素去哪里了。
在这里插入图片描述
Vue3-自学笔记_第8张图片
所有没有使用slot标签占位的元素,最终都被组件实例对象身上的$slots属性收集到,且收集到的是虚拟DOM。
在这里插入图片描述

Vue3中的attrs和slots

前提知识,在Vue3的setup函数,会默认接收两个参数,分别为propscontext

  setup(props, context) { console.log(props, context);}

在这里插入图片描述
在这里也是首先搭建一个App组件和Demo组件,并在App中使用Demo组件标签传递参数,但是并不在Demo组件中使用props配置项接收。

  <Demo msg="Hello"></Demo>

Vue3会给出提示:存在一个使用组件标签传递参数的情况,但是并没有配置props配置项接收,只要配置了props,该提示就会消失。并且,哪一个传递来的属性没有被props接收,Vue3会灵活的提示还有哪些属性没有接收。
Vue3-自学笔记_第9张图片
Vue3-自学笔记_第10张图片

凡是传递来的参数被props配置项接收了,最终都会出现在setup函数的第一个参数上,并且所有接收来的数据都为Proxy响应式的
在这里插入图片描述
并且这里会跟Vue2一样,将没有接收的数据,存放在一个地方,这个时候就用到了setup的第二个参数,该参数中有attrsemitslots属性,功能和Vue2的一样。
其中attrs属性接收所有没有被props配置项接收的数据,全部存放在这里
Vue3-自学笔记_第11张图片
当在组件标签中添加HTML元素的时候,这个时候就需要使用插槽了
在这里插入图片描述
如果使用了插槽slot标签,打印setup的第二个参数身上的slots属性查看

  <slot></slot>
  。。。。
  console.log(context.slots);

在这里插入图片描述
如果使用了命名插槽,那么defalut就会显示命名的插槽名(必须使用Vue3的语法:v-slot:)
在这里插入图片描述
当给组件标签定义了一个自定义事件,如果直接在另一个组件中触发该事件就会报错提示

  <Demo msg="Hello" sub="Vue" @say="sayHello"> </Demo>
-------------------------------------------------------
//触发自定义事件的组件中
    function say() {
      context.emit("say", 666);
    }

在这里插入图片描述
这是因为在Vue3中对于父组件给子组件绑定了一个自定义事件。需要子组件配置一个emits配置项,其值为数组,接收传来的事件名。

  emits: ["say"],
  
  setup(props, context) {
    let person = reactive({
      name: ref("张三"),
      age: ref(18),
    });
    function say() {
      context.emit("say", 666);
    }
    return {
      person,
      say,
    };

总结

  1. attrs值为对象,接收了从组件传递来的值,但是没有在props配置中声明的属性,等于vue2的this.$attrs
  2. slots收到插槽的内容,相当于vue2的this.$slots
  3. emit触发自定义事件。借助setup的第二个参数有,即context.emit()等价于vue2的this.$emit()

ref函数

设置一个简单的响应式,ref—函数参数为基本数据类型格式

基本代码如下,当尝试调用setNewInfo修改信息的时候,发现Vue并没有监测到数据的改变,从而进行响应式布局,也就是说不能直接进行如下的定义声明和修改步骤

    let name = "张三";
    let age = 18;
    function setNewInfo() {
      (name = "李四"), (age = 22);
      console.log(name, age);
    }

为了解决此问题,Vue3提供了全新的API即ref函数,接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value
如果想使用refAPI,就必须引入import { ref } from "vue";

	import { ref } from "vue";
	......
    let name = ref("张三");
    let age = ref(18);
    function setNewInfo() {
      console.log(name, age);
    }
    .....

经过打印输出,发现一个简单的字符串和数值被包装成一个对象。其中红色区域是由RefImpl构造函数实例化出来的对象。RefImpl全称Reference Implement,使用该构造函数实例化出来的对象叫引用实现的实例对象。在ref底层也是基于ObjectDefineProperty实现响应式的。对于ref的值进行了响应式处理,将值全部进行了代理放在__proto__身上,其主要目的是显示的时候看起来很简化,将不用的东西全部隐藏。
在这里插入图片描述
如下代码中,用户对某一个引用实现的实例对象进行.value操作的时候,就可以被Vue监测到从而进行响应式。
Vue3-自学笔记_第12张图片
如下修改,即可实现简单的响应式

    let name = ref("张三");
    let age = ref(18);
    function setNewInfo() {
      name.value = "李四";
      age.value = 30;
    }

但是在页面中使用的时候,就不需要进行.value的操作,可以简单的理解Vue为了能帮助我们简化操作,当解析到name的时候,就明白这是一个RefImpl的实例对象,于是就自动的去执行name.value取出值显示在页面中。

  <h1>姓名:{{ name }}h1>
  <h1>年龄:{{ age }}h1>

ref—函数参数为对象格式

添加如下代码块

    let job = ref({
      jobName: "前端程序员",
      salary: "1999",
    });
	....事件回调函数中打印输出
	console.log(job);
	....

输出job的输出结果可以发现,当job使用了ref函数的时候,其本质就会去new一个 RefImpl构造函数,所以传入ref中的对象理应在value属性中。那么执行到这里根据Vue2的思路,vue会对于一个对象解构,不断的循环遍历,为每一个数据都自动的添加上响应式。但是事实却不是这样子的。
Vue3-自学笔记_第13张图片
根据上面分析添加下面的代码。一旦添加下面的代码了,程序就会报错

     job.value.jobName.value = "后端程序员";

在报错中给出提示·,不能在一个字符串身上创建新的属性。即不能在jobName属性上继续调用.value
无法在字符串“前端程序员”上创建属性“value”
继续打印观察job.value中给的内容是什么,结果如下,value的形式为Proxy对象,这是ES6新增的数据代理Proxy代理。发现打印的结果显示:value中,可以直接使用那些对象中属性

 console.log(job.value);

在这里插入图片描述
修改代码如下,发现页面可以实现响应式效果

      job.value.jobName = "后端程序员";
      job.value.salary = 2000;

在Vue3中,对于一些基本的数据类型,采用的是ObjectDefineProperty实现数据代理。但是对于对象类型的数据,对于其引用的地址也是基于ObjectDefineProperty实现,但是对于引用地址里面的值,是基于Proxy实现数据代理的。Vue3对于不同类型的数据采用不同的数据代理方式。
Vue3-自学笔记_第14张图片

对象类型的数据,在其内部借助了Vue3的一个函数:reactive函数,该函数实现对proxy的封装。对象中的属性都会交给reactive函数的处理,最终称为proxy对象的形式。

reactive函数

作用:给对象类型的数据进行数据代理,实现响应式效果。
重点:基本数据类型不能使用reactive函数,只能使用ref函数
reactive函数是基于Proxy实现数据代理,Proxy的数据代理能够遍历深层次的对象解构

let 代理对象 = reactive({源对象}) //可以给对象或数组进行代理,返回的是Proxy的实例对象

如果想使用reactive实现数据代理,就必须引入

import { reactive } from "vue";

验证reactive不能给基本数据类型设置,如下图。一旦将一个基本数据类型交由reactive进行处理,Vue会立即报错提示。
在这里插入图片描述
将上面的对象从ref函数改为reactive函数,并打印输出,也就是说在修改数据的时候,可以直接job.属性从而修改数据,而修改数据后的结果会被vue监视到从而重新布局页面。

    let job = reactive({
      jobName: "前端程序员",
      salary: 1999,
    });
    ....
      console.log(job);
    ....

在这里插入图片描述

      job.jobName = "后端程序员";
      job.salary = 2000;

对比ref的对象操作,发现其实本质是一样的,ref函数在处理对象的时候,底层会调用reactive函数处理,所以在返回的Proxy对象中可以直接调用并修改。开发中对于对象的数据监测使用reactive的原因是可以简化一些代码。如果是ref处理,则ref会先返回一个RefImpl对象。其中所以的参数都存放在value属性中,从而造成了必须再次.value后才能获取返回的Proxy对象,获取值。相比于reactive,每一步都多了一个.value步骤。

reactive也能实现对数组的代理。

    let year = reactive(["2001", "2002", "2003"]);

在这里插入图片描述

reactive对比ref

  1. 定义角度
    *:ref通常定义基本数据类型
    *:reactivev通常用来定义对象和数组类型数据
    *:ref也可以用来定义对象和数组类型的数据,其底层会自动调用reactive函数

  2. 从原理角度
    *. ref通过Object.defineProperty方法的getset实现响应式。
    *:reactive通过Proxy实现响应式。并通过Refelct操作源对象内部数据

  3. 从使用角度
    *.ref定义的数据需要.value才能读取数据
    *:reactive定义的数据,操作和读取数据均不需要.value

响应式原理

在Vue2中响应式

对象类型基于Object.DefineProerty对属性的读取,修改,拦截,,代理。
数组类型基于重新更写数组的一些列方法来实现数据拦截代理。对于数组的原方法进行了包装
但是Vue2的响应式存在一些问题造成了响应式的不方便。

  1. 在对象中新增属性,删除属性,Vue2无法及时更新页面。
    Vue.set()this.$set()对新增的属性设置响应式。Vue.delete()this.$delete()实现对象属性的删除。
  2. 直接通过下标修改数组的时候,Vue2无法更新页面。通过能够影响原始数组的方法

Vue3的响应式

先简单写一段Vue2的响应式代码。在控制台对p进行增删查改,可以发现修改某个数据的时候,页面监测到并成功更新。但是删除某个数据或添加新的数据的时候,页面不能及时更新。这就是Object.DefineProerty带来的问题。不能同时对一个对象中的所有属性进行劫持代理,只能循环代及其不方便。且问题很多。

  <h1 id="name">姓名:</h1>
  <h1 id="age">年龄:</h1>
  <script>
    let DomObj = {
      name: document.querySelector('#name'),
      age: document.querySelector('#age')
    }
    let per = {
      name: '张三',
      age: 18
    }
    DomObj.name.innerHTML = per.name
    DomObj.age.innerHTML = per.age
    let p = {}
    Object.defineProperty(p, 'name', {
      configurable: true,
      get() {
        return per.name
      },
      set(value) {
        per.name = value
        DomObj.name.innerHTML = per.name
      }
    })
    Object.defineProperty(p, 'age', {
      configurable: true,
      get() {
        return per.age
      },
      set(value) {
        per.age = value
        DomObj.age.innerHTML = per.age
      }
    })

但在Vue3中,对于Vue2的问题都被解决了,可以直接修改某个对象的值,并且添加数据的时候不需要添加过多的操作。

Proxy代理

    let per = {
      name: '张三',
      age: 18
    }
    //p为代理对象,per为源对象
     let p = new Proxy(per, {
      get(target, key) { //target就是per对象,key为字符串类型的属性名
        console.log("读取", target, key)
        return target[key]
      },
      set(target, key, value) {
        console.log("修改", target, key, value)
        target[key] = value
      }
    })

在这里插入图片描述
在这里插入图片描述
这里需要注意的是当添加一个新的属性的时候,走的是Proxy中的set函数,也就是说set函数能够实现修改数据和添加数据两个功能

      set(target, key, value) {
        console.log("修改、添加", target, key, value)
        target[key] = value
      }

Vue3-自学笔记_第15张图片
当需要修改的时候 Proxy提供了deleteProperty方法来实现删除数据并监测,该方法接收一个返回值,通常在该方法中将delete targert[value]的结果布尔值作为返回值。当调用delete删某个属性的时候,就会自动去执行deleteProperty方法。

      deleteProperty(target, key) {
        return delete target[key]
      }

在Vue3中,get函数实现读取的功能,set函数实现修改和添加的功能,deleteProperty实现删除效果。

Reflect

作用“:对源对象的某个属性进行操作。
Reflect并不是一个构造函数,不能使用new关键字
如下常见的Reflect静态方法。

Reflect.get(target, key) //返回获取到的值
Reflect.set(target, key, value) //返回布尔值,表示成功设置或许修改key的值。如果多次多某个key进行设置,则以第一次为主。
//Object.defineProperty不能重复设置同一个key,否则会报错
Reflect.deleteProperty(target, key) //返回布尔值

使用Reflect可以让程序更加灵活健壮。由于设置失败不会报错,而是通过返回的布尔值进行相应的操作,对比Object.defineProperty设置失败就报错有很大提升。

computed计算属性

Vue3虽然向下兼容Vu2计算属性的写法,但是不推荐在Vue3中使用Vue2的写法
Vue3中要想使用计算属性computed,就必须引入该API。

import { computed } from "vue";

设置只读性的计算属性,即简写格式

因为是组合式API,所以需要放在setup函数中,并且在调用的时候使用computed()
computed()参数传递一个函数的时候。就是简写形式,即只有get的可读情况。

setup() {
	。。。。。
    let fullName = computed(() => {
      return person.firstName + "-" + person.lastName;
    });
    return {
      fullName,
    };

设置可读可修改计算属性

即在computedAPI中传入配置对象,对应的写法和Vue2一样

setup(){
	......
    let fullName = computed({
      get() {
        return person.firstName + "-" + person.lastName;
      },
      set(value) {
        let arr = value.split("-");
        person.firstName = arr[0];
        person.lastName = arr[1];
      },
    });
    .....
    }

watch监视属性

在Vue3中可以直接使用Vue2的watch配置项进行侦听。(不需要引入watch的API,且不写在setup函数中)

 watch: {
    sum(newV, oldV) {
      console.log("sum修改了了", newV, oldV);
    },
  },
  watch: {
    sum: {
      immediate: true,
      handler(newV, oldV) {
        console.log("sum修改了了", newV, oldV);
      },
    },
  },

Vue3中要想使用自己的watch,就必须引入该API,并且需要写在setup函数中

import { watch } from "vue";

在Vue3中将watch设置为一个API,该方法接收三个参数:watch(监视的属性,执行的回调函数,设置配置项)

监听ref中的单个属性

当监视的参数为单个属性的时候,在回调函数中接收的新值和旧值均为简单数据类型

  setup() {
    let sum = ref(0);
    watch(sum, (nv, ov) => {
      console.log("sum", nv, ov);
    });
    return {
      sum,
    }

在这里插入图片描述

监听ref中的多个属性

当监视的属性为多个的时候在回调函数中接收的新值和旧值为数组

 setup() {
    let sum = ref(0);
    let msg = ref("你好");
    watch([sum, msg],(nv, ov) => {
        console.log("sum", nv, ov);
      }, { immediate: true }); //配置watch的第三个参数
    return {
      sum,
      msg,
    };
  },

修改msg的时候,被watch监视,从而打印新值和旧值,发现每一个新值和旧值都包含sum和msg组成的数组。
在这里插入图片描述

监听reactive中的对象

当使用reactive定义一个对象的时候。会发现打印的newValue和oldValue打印如下,这个地方出现一个问题,即新值和旧值的结果都以新值为主,即使使用ref定义一个对象,结果也一样,因为ref处理对象也是底层调用reactive实现。且ref声明的对象,在监视的时候需要使用person.value
使用reactive定义一个对象Person的时候,无法获取旧值,且该监视是强制开启深度遍历。设置deep:false无效

    let person = reactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    watch(person, (newValue, oldValue) => {
      console.log("person监视到了", newValue, oldValue);
    });

在这里插入图片描述
下面是修改jobName的情况,发现没有设置deep:true的时候,也能监视深层次的数据。即使设置为false也能监测。
Vue3-自学笔记_第16张图片
当使用readtive监视一个对象的某个属性的时候,发现Vue直接给出提示。也就是说无法直接监视一个对象的某个属性,那么最简单的修改就是将监视的值包装成一个箭头函数返回。

   watch(person.name, (newValue, oldValue) => {
      console.log("person监视到了", newValue, oldValue);
    });

在这里插入图片描述
将一个普通的对象属性放在箭头函数中的返回值中返回

    watch(() => person.name,(newValue, oldValue) => {
        console.log("person监视到了", newValue, oldValue);
      }

在这里插入图片描述

使用reactive监视对象中的某些属性,就是将对象中的普通属性,放在箭头函数中的返回值,并将箭头函数存在一个数组中

    watch([() => person.name, () => person.job.j1.jobName],(newValue, oldValue) => {
        console.log("person监视到了", newValue, oldValue);
      }
    );

在这里插入图片描述
当使用reactive监视一个对象中的某个对象,则deep配置项有效。以下代码将job对象放在一个函数中返回,所以可以理解为一个新的引用,不再是reactive,需要开启深度监视deep。

    watch(() => person.job,(newValue, oldValue) => {
        console.log("person监视到了", newValue, oldValue);
      },{ deep: true }
    );

但是如下代码设置deep配置项就无效

    watch(person.job, (newValue, oldValue) => {
      console.log("person监视到了", newValue, oldValue);
    });
总结
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
- 
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
	console.log('sum变化了',newValue,oldValue)
},{immediate:true})

//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
	console.log('sum或msg变化了',newValue,oldValue)
}) 

/* 情况三:监视reactive定义的响应式数据
			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
*/
watch(person,(newValue,oldValue)=>{
	console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效

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

//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
	console.log('person的job变化了',newValue,oldValue)
},{immediate:true})

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

watch时是否.value的问题

当使用ref设置一个简单数据类型的时候

在配置wathc监视的时候,使用 .value,这个时候Vue就给出了报错提示说明:如下,简而言之,就是sum.value的值即0已经不是一个ref结构的数据了,而是一个普通的数值,如果是这样子那么就需要采用函数返回的形式返回该基本数据类型。即 () => sum.value,

    let sum = ref(0);
    .......
    watch(sum.value, (nv, ov) => {
      console.log("sum被监视到了", nv, ov);
    });

在这里插入图片描述

当监视ref定义的对象的时候出现的问题

在如下代码中person对象使用ref定义。并且为该对象设置了监视,结果测试控制台打印,发现该对象的监视并没有成功,这是什么原因?

    let person = ref({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    watch(person, (nv, ov) => {
      console.log("person被监视到了", nv, ov);
    });

person为一个ref定义的对象,即没有开启深度遍历的对象

1.第一种解决办法就是直接添加deep配置项。当一个对象中某个数据改变的时候,该对象内部是不被监视到的。如let obj = {a:1} 当修改其内部a的值,不被监视,而一旦修改obj的地址,就会被监视到。

    watch(person,(nv, ov) => {
        console.log("person被监视到了", nv, ov);
      },{ deep: true }
    );
  1. 第二种方式就是借助与reactive函数,这里需要知道,在ref声明的person对象结构中,所有的数据都是存放在value中,而该对象是通过reactive实现的,而reactive监视的对象是强制开启深度遍历的。
    watch(person.value, (nv, ov) => {
      console.log("person被监视到了", nv, ov);
    });

watchEffect函数

wathcEffect函数的出现,解决了watch配置的繁琐问题。
watchEffect函数更加智能,能够自行分辨哪些数据是否监视。即在该函数中用到的数据均会被监视
watchEffect也属于组合式API,所以需要引入。
在调用的时候直接使用watchEffect(()=>{})

import { watchEffect } from "vue";
........................
    let person = reactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    watchEffect(() => {
      console.log("wathcEffect调用了");
    });
 ............................

但是按照如上代码,没有点击页面,控制台自动打印wathcEffect调用了。也就是说watchEffect默认开启了immediate:true,即使点击了页面,watchEffect函数也不会调用。(这是因为该函数中没有所依赖的数据。)
Vue3-自学笔记_第17张图片
当在wathchEffect函数中配置了一些vm实例身上的属性的时候,该函数就能自动的获取并配置哪些数据是需要被监视的,且对于其他没有出现在内部的数据,不会进行处理。

    watchEffect(() => {
      let a = sum.value; //sum.value被监视
      let b = person.job.j1.jobName; //person.job.j1.jobName被监视
      //其他属性均不会被监视
      console.log("wathcEffect调用了");
    });

总结

  • watch要指明监视的属性和监视所需要执行的回调函数
  • watchEffect不需要指明哪些属性,监视的回调中用到哪些属性,就监视那个属性
  • watchEffect类似计算属性computed。但是计算属性注重返回值中计算出来的结果。而watchEffect注重监视的过程。

Vue3生命周期

以下的Vue3生命周期图是旧版
Vue3-自学笔记_第18张图片
以下这个是新版的vue3生命周期图,目前先理解上面的旧版,本质两个图是一样的
Vue3-自学笔记_第19张图片

通过配置项形式使用生命周期钩子,需要区别的是在Vue3中,不在有销毁这个概念了,取而代之的是对立的挂载和卸载,也就是说最后两个钩子修改了,从原先的destroy均换成了unmounted。如果在vue3中使用了vue2相关的destroy销毁函数,则无效
以下是配置形式,同时还有组合式API的格式,但是写成组合式API的生命周期函数,需要在setup函数入口中写,且每一个生命周期函数都需要更换名字。

  setup() {
    let sum = ref(0);
    return {
      sum,
    };
  },
  beforeCreate() {
    console.log("--beforeCreate");
  },
  created() {
    console.log("--created");
  },
  beforeMount() {
    console.log("beforeMount");
  },
  mounted() {
    console.log("muonted");
  },
  beforeUpdate() {
    console.log("beforeUpdata");
  },
  updated() {
    console.log("updated");
  },
  beforeUnmount() {
    console.log("beforeUnmount");
  },
  unmounted() {
    console.log("unmounted");
  },
};

因为生命周期函数采用组合式API方式使用,所以需要引入。每一个都需要传入一个回调函数作为参数进行调用

import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from "vue";
--------------
setup(){
	//调用
	onBeforeMount(()=>{})
}

需要注意的是在Vue3中,如果使用组合式API的形式,那么就不存在beforeCreatecreated这两个生命周期函数,这两个函数都被一个setup函数集合在一起。其余的生命周期函数不变只是在首部均添加上了on表明是在setup函数内使用的生命周期函数。
Vue3-自学笔记_第20张图片
如果组合式的生命周期函数和配置项形式的生命周期函数一起出现,则在setup中的生命周期函数优先级高,会先执行。

生命周期函数可以多次调用,会按照代码中的位置顺序执行,主要作用是:如果接收一个别的项目,其onMounted函数中实现了很多逻辑功能,你不确定是否可以修改,但是需要添加你自己的功能代码,这个时候就可以再次写一个onMounted函数。
Vue3-自学笔记_第21张图片

自定义hook函数

hook本质是一个函数,将setup函数中使用的Composition API组合式API进行了封装。
作用:将一些需要复用的功能,代码,组合式API封装起来使用。
类似Vue2的mixin混入。
使用步骤:1:通常在src文件中创建一个新的hook文件夹,里面存放封装好的文件,建议取名字的时候使用如useName这种形式创建,让人见者知其意。(如果在hook函数中封装了一些数据,而这些数据在组件中需要被使用到,那么需要在hook函数中将数据返回出去)
如下代码,在一个组件中使用获取鼠标坐标的功能,但是当另一个人也需要使用该功能的时候该如何处理,不能将代码复制过去,必须实现代码复用才行,于是自定义hook函数的出现了。

 setup() {
    let point = reactive({
      x: 0,
      y: 0,
    });
    function getPoint(e) {
      point.x = e.pageX;
      point.y = e.pageY;
    }
    onBeforeMount(() => {
      window.addEventListener("click", getPoint);
    });
    onBeforeUnmount(() => {
      window.removeEventListener("click", getPoint);
    });
    return {
      point,
    };
  },

将代码全部封在一个文件中
在这里插入图片描述

import { onBeforeMount, reactive, onBeforeUnmount } from 'vue'
// hook本质是一个函数
// 需要暴露外部使用
export default function () {
  let point = reactive({
    x: 0,
    y: 0,
  });
  function getPoint(e) {
    console.log("执行了")
    point.x = e.pageX;
    point.y = e.pageY;
  }
  onBeforeMount(() => {
    window.addEventListener("click", getPoint);
  });
  onBeforeUnmount(() => {
    window.removeEventListener("click", getPoint);
  });

  // 将内部数据返回
  return point
}

在用到该方法组件中引入

// 引入自定义hook函数
import usePoint from "../hook/usePoint";
----------
let point = usePoint(); //需要用到内部数据,即point,所以需要返回值
----------

toRef

toRef也是组合式API中的一个,所以也需要单独引入
作用:创建一个ref对象,其value值指向了另一个对象中的某个属性。将一个响应式对象中层次很多的属性提出来并提供给外面使用且不丢失响应式效果。
语法:const xxx = toRef(对象,‘该对象中的属性’)

当在setup中有一个结构及其复杂的对象,并将该对象不加以任何处理直接return返回的时候。如果想在模板中使用,那么就必须一层一层的.调用下去。
Vue3-自学笔记_第22张图片
但是按照上面的写法,就违背了vue的推荐做法,将模板中的代码显的即为复杂。为了方便,首先想到的办法就是在return中进行处理。但是这么处理就会丢失响应式效果,将该对象中的某个属性取出,相当于将一个字符串或数值取出,这样子显然是没有响应式的。比如person.name就是‘张三’,一个字符串赋值给name属性是没有响应式效果的。
Vue3-自学笔记_第23张图片
如下两副图解释
Vue3-自学笔记_第24张图片

Vue3-自学笔记_第25张图片
这个时候toRef的功能就出来了
引入toRefapi

import { toRef } from "vue";

基本结构如下,观看打印输出的两种结果区别。第一个将一个对象中的某个属性直接取出,丢失了响应式效果。而第二个考虑很多,明知到取出来会丢失响应式效果,于是主动再次添加上响应式即可,再次转换为ref对象即可。
这里需要重点记忆toRef中监视该对象中的name属性,实则监视的是原reactive中的name。只需要证明修改x2的name的时候person中的name也修改即可。

  setup() {
    let person = reactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    let x1 = person.name;
    let x2 = toRef(person, "name");
    console.log(x1);
    console.log(x2);
。。。。。。。。。。。。。。。。。。省略
  },

Vue3-自学笔记_第26张图片
为了验证上面的结论,我们修改代码如下,点击按钮的时候修改的是toRef处理的数据,对比结果发现,当toRef处理过后的nameage值改变的时候,person对象中的数据也跟着改变了。

//结构代码
  <h2>姓名:{{ name }}</h2>
  <h2>年龄:{{ age }}</h2>
  <h2>工作:{{ jobName }}</h2>
  <hr />
  <h3>我是person中的姓名:{{ person.name }}</h3>
  <h3>我是person中的年龄:{{ person.age }}</h3>
  <button @click="name += '~'">点击姓名</button>
  <button @click="age++">点击年龄</button>
  <button @click="jobName += '!'">点击增加</button>
----------------------
 return {
      /* toRef */
      name: toRef(person, "name"),
      age: toRef(person, "age"),
      jobName: toRef(person.job.j1, "jobName"),
      /* 源数据 */
      person,
    };

Vue3-自学笔记_第27张图片
Vue3-自学笔记_第28张图片
注意不能使用ref去重新定义数据,因为ref会将该数据取出后,重新定义为一个对象,源person中的数据无法与ref重新定义后的数据保持同步

return {
      /* ref */
      name: ref(person.name),
      age: ref(person.age),
      jobName: ref(person.job.j1.jobName),
      /* 源数据 */
      person,
    };
  },

Vue3-自学笔记_第29张图片

toRefs

作用和toRef一样,区别是toRef只能单独处理一个数据,而toRefs能同时处理多个。
格式:const xxx = toRefs(对象)
将以下代码打印输出结果如下 let x = toRefs(person);发现其对每一个一级属性都进行了处理,全部转换为RefImpl引用实现实例对象处理。
在这里插入图片描述
代码如下,将toRefs返回的对象展开,取出里面的值,里面所以的值均经过Proxy代理到了person对象上。也就是说,toRefs中的代理值改变,person中的值也会跟着改变,从而达到了数据统一的目的。

 return {
      /* toRefs */`在这里插入代码片`
      ...toRefs(person),
      /* 源数据 */
      person,
    };

其他Composition API

shallowReactive与shallowRef

shallowReactive:只处理对象最外层属性的响应式(浅响应式)
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
使用时机

  1. 若有一个对象数据,结构较深,但变化时只是是最外层属性变化==>shallowReactive
  2. 若有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换==>shallowRef
  3. shallowRef,shallowReactive最外的数据是响应式,区别是对内部数据如何处理

因为 shallowRef,shallowReactive都是组合式API,所以需要从vue中引入

import {  shallowReactive, shallowRef } from "vue";

基本代码如下

  setup() {
    let person = shallowReactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    return {
      /* 源数据 */
      person,
    };
  },

页面中的每一个按钮都点击查看效果,发现在person对象中其job对象中里面的j1对象中的数据未进行改变。即shallowReactive:只处理对象最外层属性的响应式
Vue3-自学笔记_第30张图片
Vue3-自学笔记_第31张图片
当对一个基本数据类型使用shallowRef定义的时候,页面能够正常处理响应式

    let x = shallowRef(0);

当使用shallowRef定义一个对象的时候,发现该内部的x.data失去了响应式。(但是x还有具有响应式的,当整个对象地址改变的时候,页面变化,即将x修改为一个新的对象,x={…})

    let x = shallowRef({
      data: 0,
    });
    consolo.log(x)

打印shallowRef定义的x如下,发现x是响应式的RefImpl,但是内部的数据是一个简单的Object对象,所以无响应式。
Vue3-自学笔记_第32张图片

readonly和shallowReadonly

  • readonly:让一个响应式的数据变为只读,(多层次对象的全部属性变为只读,深只读
    *:接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。返回一个proxy的只读代理
  • shallowReadonly:让一个响应式的数据变为只读。(若为对象,只针对最外层的属性设置为只读,浅只读

应用场景:当多个组件共享一个响应式数据的时候,希望其中的某个组件允许用该响应式数据,而不允许修改(即接收到该响应式数据的时候立即设置为只读),但是其中组件可以对该响应式数据进行修改。

import {readonly, shallowReadonly } from "vue";

基本代码如下,将原先是reactive声明的响应式对象person设置为只读,调用readonly(person)将响应式对象person作为参数传入该方法中。返回新的person供外部使用。
当点击页面按钮的时候,会发现Vue监测到你想修改只读的数据类型,于是控制台给出警告

  setup() {
    let sum = ref(0);
    let person = reactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    person = readonly(person);

Vue3-自学笔记_第33张图片
当改为shallowReadonly(person)的时候,意思是将该属性的最外层设置为只读,深层次的依旧是响应式的。如图当点击页面的时候,j1对象中的数据依旧为响应式

   person = shallowReadonly(person);

Vue3-自学笔记_第34张图片
readonly, shallowReadonly不仅可以将reactive定义的对象设置为对应的只读,也可以将ref定义的简单数据类型设置为只读。
但是shallowReadonly处理ref定义的对象的时候,该对象最外层数据依旧为响应式

toRaw与markRaw

toRaw

作用:将一个由reactive生成的响应式对象转为普通对象。ref定义的数据无效
场景:读取响应式对象对应的普通对象,对这个普通对象的所有操作不会引起页面更新。
引入该API

import {toRaw } from "vue";

基本代码如下,person源对象是一个readtive定义的响应式数据,在函数中使用toRaw方法将该响应式对象转换为普通对象,使其失去了响应式

   let person = reactive({
      name: "张三",
      age: 33,
      job: {
        j1: {
          jobName: "Vue",
        },
      },
    });
    function addRaw() {
      let p = toRaw(person);
      console.log(p);
    }

在这里插入图片描述

markRaw

作用:标记一个对象,使其永远不会变为响应式数据。
场景:有一个很复杂的对象,如果让vue渲染一个几乎不改变的对象数据,跳过响应式转换处理可以提高性能。
引入该API

import { markRaw } from "vue";

如果初始的时候定义了一个响应式对象,后期突然想给该对象添加一个数据,该数据很复杂且基本不变,那么就可以给该数据打一个标记,告诉vue这个数据不需要进行响应式proxy处理,跳过代理操作,提高性能。(proxy代理的对象添加的数据也是响应式的)

    function addRaw() {
      let car = { carName: "宝马" };
      person.car = markRaw(car);
    }

上面的代码给car对象打了一个标记,告诉vue对该数据不需要进行响应式处理。所以当点击页面的时候不会改变该数据
在这里插入图片描述

customRef

作用:建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
vue中ref相当于一个精心封装的函数,其内部功能齐全,而customRef是只有部分功能,可以自己根据需求完善。即使是自定义ref,也不可能自己手写,需要借助customRef方法

import {customRef} from "vue";

在自定义的ref中,需要将调用的customRef方法的结果返回

    // 自定义一个ref
    function myRef(value) {
      return customRef();
    }
    let msg = myRef("hello");

但是这么使用customRef方法就会报错,vue要求,该方法必须传入一个函数作为参数
在这里插入图片描述
但是当设置完函数作为参数后,控制台还给出了报错信息,vue要求该函数必须返回一个对象

    function myRef(value) {
      return customRef(() => {});
    }

在这里插入图片描述
这样子设置返回对象后,控制台还是给出报错信息,即该函数的返回值对象中需要包含get函数set函数

   function myRef(value) {
      return customRef(() => {
        return {};
      });
    }

在这里插入图片描述

配置完成如下,其中也是基于响应式,这是因为自定义的ref数据也需要遵循响应式。
其中get是读取的时候调用,set是修改的时候调用

    function myRef(value) {
      return customRef(() => {
        return {
          get() {},
          set() {},
        };
      });
    }

customRef的参数中,该回调函数接收两个值分别为:track跟踪方法trigger触发方法
track跟踪方法作用:告诉get函数,追踪自定义ref中值的变化。否则即使值改变,get函数也不会重新执行将值返回

trigger触发方法作用:每次修改自定义ref函数中的值的时候,都需要在set中去调用trigger()重新渲染模板。
修改值的时候执行顺序:先执行set函数将value的值修改为新值,调用trigger方法重新渲染模板,当渲染模板的时候,需要再次读取msg的值,于是又进入get函数中读取值,因为get函数中有track函数。于是get函数将return的返回值作为msg的最终值。

    // 自定义一个ref
    function myRef(value) {
      return customRef((track, trigger) => {
        return {
          get() {
            console.log("get读取了");
            track(); //必须放在这里,告诉vue需要跟踪value值的变化
            return value;
          },
          set(newValue) {
            console.log("set修改了", newValue);
            value = newValue; //重新修改value的值
            trigger(); //通知vue重新解析模板数据
          },
        };
      });
    }
    let msg = myRef("hello");

将功能代码修改如下,设置输入值后延迟1秒显示,注意防抖
如果这么写代码会有bug,每次输入都会都会开启一个新的定时器,上一个trigger函数调用后,会重新渲染模板,导致bug出现

          set(newValue) {
            console.log("set修改了", newValue);
            // 设置输入值后延迟1秒显示,注意防抖
            setTimeout(() => {
              value = newValue; //重新修改value的值
              trigger(); //通知vue重新解析模板数据
            }, 500);
          },

正确做法就是采用防抖,每次开启新的定时器之前,清除上一个开启的定时器

    // 自定义一个ref
 function myRef(value, delay) {
      let timer;
      return customRef((track, trigger) => {
        return {
          get() {
            console.log("get读取了");
            track(); //必须放在这里,告诉vue需要跟踪value值的变化
            return value;
          },
          set(newValue) {
            console.log("set修改了", newValue);
            // 清除定时器
            clearTimeout(timer);
            // 设置输入值后延迟1秒显示,注意防抖
            timer = setTimeout(() => {
              value = newValue; //重新修改value的值
              trigger(); //通知vue重新解析模板数据
            }, delay);
          },
        };
      });
}
    let msg = myRef("hello", 500);

provide提供和inject注入

这两个API同样是组合式API,他们的作用是实现祖孙之间通信。(父子之间也可以使用,但是可以使用更简单的props)
祖先组件使用provide(别名,数据)来提供数据
后代组件使用inject(别名)来接收数据
provide 和 inject 通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。

这个 provide 选项应当是一个对象或是返回一个对象的函数。这个对象包含了可注入其后代组件的属性。你可以在这个对象中使用 Symbol 类型的值作为 key。
Vue3-自学笔记_第35张图片
在提供数据的祖先组件中引入provideAPI

import {provide } from "vue";
  setup() {
    let per = reactive({
      name: "张三",
      age: 21,
    });
    provide("perData", per);//注入名和注入值
    return { ...toRefs(per) };
  },

在接收数据的后代组件中引入injectAPI

import { inject } from "vue";

打印输出接收到的值,发现也是一个响应式结构。

  setup() {
    let perRes = inject("perData");
    console.log(perRes);
    return { ...toRefs(perRes) };
  },

在这里插入图片描述

响应式数据判断

isRef

检查一个值是否为ref对象

isReactive

检查一个对象是否由reactive定义

isReadonly

检查一个数据是否是由readonly创建的只读代理

isProxy

检查一个对象是否有reactive或readonly方法创建的代理

    let per = reactive({
      name: "张三",
      age: 21,
    });
    let per2 = ref({
      name: "张三",
      age: 21,
    });
    let sum = ref(0);
    let r = readonly(per);
    
    console.log(isRef(sum)); //true
    console.log(isReactive(per)); //true
    console.log(isReactive(per2)); //false
    console.log(isRef(per2)); //true
    console.log(isReadonly(r)); //true,readonly设置per为只读代理

Fragment

  • vue2中组件必须有一个根标签
  • vue3中组件可以没有根标签,底层将多个标签放在一个Fragment虚拟元素中
  • 减少标签层级,减少内存占用
    如图,没有根标签的时候,vue开发者工具会提示,这些没有根标签的元素会被包裹在fragment中
    在这里插入图片描述

Teleport

作用:Teleport标签是一种能够将我们的组件HTML结构移动到指定位置的技术
当存在一个嵌套很深的组件关系的时候,对应的某个组件如果是根据v-if来确定是否显示或隐藏那么就会存在一个问题,即当该组件显示的时候,会撑大原有的组件。这样子是很不好的。虽然可以使用定位解决,但是定位的嵌套关系很难维护。
Vue3-自学笔记_第36张图片
Vue3-自学笔记_第37张图片
如上两副图对比就会发现,突然出现的图片会影响其他组件的布局。那么如何控制。这个时候就引入了Teleport标签
teleport标签有一个to属性,该属性内部可以写HTML元素或写选择器,如body,最终就将内部结构放入body中,而非源组件中。

<template>
  <div>
    <button @click="isShow = true">点击显示</button>
    <teleport to="body" v-if="isShow">
      <div class="mask">
        <div class="dialog">
          <h1>我是对话窗口</h1>
          <button @click="isShow = false">点击隐藏</button>
        </div>
      </div>
    </teleport>
  </div>
</template>

Vue3-自学笔记_第38张图片

Suspense组件

作用:等待异步组件时渲染一些额外内容,让应用有更好的用户体验
前提知识,同步加载组件和异步加载组件
两种引入方式均在低速网络中引入

同步加载组件

就是平常import引入的语句。但是这种引入存在一个问题,如果一个组件嵌套多个其他组件或使用到了其他组件。由于采用同步引入的方式,当网络发送堵塞的时候,整个页面是处于一起加载的情况,一旦网络请求成功就一起显示。即当HTML代码执行到JS部分的代码,就会去执行JS部分,由于JS代码会引起HTML代码的阻塞,而JS发送网络堵塞,就这造成了无法成功解析HTML显示。
Vue3-自学笔记_第39张图片

异步加载组件

采用vue提供的apidefineAsyncComponet异步引入组件。该方法需要传入一个函数作为参数,且参数需要有一个返回值,通常在这里采用按需引入import引入其他组件。

import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("@/components/Child.vue"));
export default {
  name: "App",
  components: { Child },
};

这个时候当网络堵塞,就会先执行HTML结构,当渲染到JS部分组件标签的时候就会放入其他队列执行,优先执行完HTML。让用户线看见页面。
但是这方式存在一个页面问题,即先加载是数据先显示给用户,后加载的页面信息会突然出现,造成页面抖动,这个时候就引入了Suspense组件
Suspense标签组件有两个插槽:defaultfallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
在初始渲染时, 将在内存中渲染其默认的插槽内容。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容。当所有遇到的异步依赖都完成后, 会进入完成状态,并将展示出默认插槽的内容。
如果在初次渲染时没有遇到异步依赖, 会直接进入完成状态。

既然要使用带名字的插槽:defaultfallback,那么就必须使用一个template标签包裹,并使用vue3的v-slot:指定存放的插槽

<template>
  <div class="App">
    <h1>我是App组</h1>
    <Suspense>
      <template v-slot:default>
        <Child></Child> //异步加载区域
      </template>
      <template v-slot:fallback>
        <h1>loading处理,稍等片刻。。。</h1>
      </template>
    </Suspense>
  </div>
</template>

```javascript
import { defineAsyncComponent } from "vue";
const Child = defineAsyncComponent(() => import("@/components/Child.vue"));

Vue3-自学笔记_第40张图片

当使用异步引入组件配合Suspense组件的时候,可以在组件中使用一些异步操作,在上述代码的基础上,在Child组件中添加如下异步操作。可以发现该异步操作可能会导致组件加载的时机。通俗讲就是加载的的时候,会发现加载中字样显示的时候,网络不转圈加载了,但是Child组件没有加载出来,这是由内部异步操作引起的

  setup() {
    let data = ref(0);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ data });
      }, 1000);
    });
  },

代码也可以该成如下

  async setup() {
    let data = ref(0);
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ data });
      }, 3000);
    });
    return await p;
  },

注意:在同步引入组件的情况下,不能在setup函数中使用async函数

其他

  1. 全局API的转移
Vue2 Vue3
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

2.过渡类名的更改完善

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 的修饰符,同时也不再支持config.keyCodes
  • 移除v-on.native修饰符
父组件
<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>
子组件
<script>
  export default {
    emits: ['close']//click不写默认为原生事件,写了就为自定义事件
  }
</script>
  1. vue3移除了过滤器,推荐使用计算属性代替过滤器

你可能感兴趣的:(vue.js,前端)