• Vue3-初识Vue3、创建Vue3工程、vue3组合式API(setup、ref函数、reactive函数)、响应式原理、计算属性、监视属性


    Vue3(1)

    一、Vue3简介

    2020年9月18日,Vue.js发布了3.0版本,代号:One Piece(海贼王)

    耗时两年多,2600+次提交、30+个RFC、600+次PR、99位贡献者

    Vue3带来了什么:

    1、性能上的提升

    • 打包大小减少42%
    • 初次渲染块55%、更新渲染块133%
    • 内存减少54%

    2、源码的升级

    • 使用Proxy代替defineProperty实现响应式
    • 重写虚拟DOM的实现和Tree-Sgaking

    3、拥抱TypeScript

    • Vue3可以更好的支持TypeScript

    4、新特性

    (1)Composition API(组合API)

    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject

    (2)新的内置组件

    • Fragment
    • Teleport
    • Suspense

    (3)其他改变

    • 新的生命周期钩子
    • data选项应始终被声明为一个函数
    • 移除keyCode支持作为v-on的修饰符

    二、创建Vue3.0工程

    1、使用vue-cli创建

    官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

    // 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
    vue --version
    // 安装或者升级你的@vue/cli
    npm install -g @vue/cli
    // 创建
    vue create vue_test
    // 启动
    cd vue_test
    npm run serve
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、使用vite创建

    官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
    vite官网:https://vitejs.cn

    • 什么是vite?——新一代的前端构建工具

    • 优势如下:

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

      2.轻量快速的热重载(HMR)

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

    // 创建工程
    npm init vite-app <project-name>
    // 进入工程目录
    cd <project-name>
    // 安装依赖
    npm install
    // 运行
    npm run dev
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、常用的Composition API(组合式API)

    1、拉开序幕的setup

    之前我们在vue2中都是多个配置项组合在一起(选项式API:data、method那些),在vue3中,换了一种方式,那就是Composition API(组合式API),而setup则是组合式API表演的舞台,组件中所有的数据和方法都写在setup函数中。

    这个setup函数其实有两种返回值:

    1. 返回一个对象

    2. 返回一个渲染函数

    用的最多的就是返回一个对象,把用到的所有属性、方法都返回出去,方便在模板中使用(当然,现在有了setup语法糖,后面再说)

    <template>
      <h1>个人信息h1>
      <h2>姓名:{{ name }}h2>
      <h2>年龄:{{ age }}h2>
      <button @click="sayHello">打招呼button>
    template>
    
    <script>
    import { h } from "vue";
    export default {
      name: "App",
      // 此处只是测试一下setup,暂时不考虑响应式的问题
      setup() {
        // 数据
        let name = "potato";
        let age = 18;
    
        // 方法
        function sayHello() {
          alert(`我叫${name},我${age}岁了`);
        }
          
        // 返回一个对象
        return {
          name,
          age,
          sayHello,
        };
    
        // 返回一个渲染函数
        // return () => {return h('h1','哈哈哈哈')};
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    注意:不要把setup和vue2中的选项式API混用,因为setup中访问不到vue2中的属性方法(但2可访问3),如果混用的话,有重名setup优先。

    还有,setup前不要加async,因为返回值会变成promise,而不是我们return的那些属性和方法

    2、ref函数

    还记得vue2的ref属性吗,vue2的ref类似于id。复习Vue2的ref属性

    而Vue3的ref是一个函数。

    作用:定义响应式的数据。

    例:点击按钮修改信息

    <template>
      <h1>个人信息h1>
      <h2>姓名:{{ name }}h2>
      <h2>年龄:{{ age }}h2>
      <button @click="changeInfo">修改信息button>
    template>
    
    <script>
    import { ref } from "vue";
    export default {
      name: "App",
      setup() {
        // 数据
        let name = ref("potato");
        let age = ref(18);
    
        // 方法
        function changeInfo() {
          // 直接这样写的话是不行的,因为ref把数据包装成了对象,对其操作时要加上‘.value’
          // name= "李四";
          // age= 13;
          // console.log(name, age);
          name.value = "李四";
          age.value = 13;
        }
    
        // 返回一个对象
        return {
          name,
          age,
          changeInfo,
        };
      },
    };
    script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    const xxx = ref(initValue)创建了一个包含响应式数据的引用对象(RefImp对象
    在js中操作数据:count.value
    在模板中读取数据:

    {{count}}
    (这里会自动读取value属性)

    请添加图片描述

    其实ref可以接受的数据不只是基本数据类型,复杂数据类型也可以,只不过比较麻烦,操作时都要.value

    <template>
      <h1>个人信息h1>
      <h3>职业:{{ job.type }}h3>
      <h3>工作地点:{{ job.address }}h3>
      <button @click="changeInfo">修改信息button>
    template>
    
    <script>
    import { ref } from "vue";
    export default {
      name: "App",
      setup() {
        // 数据
        let job = ref({
          type: "前端工程师",
          address: "广州",
        });
    
        // 方法
        function changeInfo() {;
          job.value.type = "后端工程师";
          job.value.address = "深圳";
        }
    
        // 返回一个对象
        return {
          job,
          changeInfo,
        };
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    基本数据类型和对象数据类型实现响应式的方式不同,前者依然是Object.defineProperty()的get与set,后者则在内部求助了reactive函数

    3、reactive函数

    作用:定义一个对象类型的响应式数据(基本类型不要用reactive,要用ref)

    <script>
    import { ref, reactive } from "vue";
    export default {
      name: "App",
      setup() {
        // 数据
        let job = reactive({
          type: "前端工程师",
          address: "广州",
        });
        // 数组也可以
        let hobby = reactive(["吃饭", "睡觉", "打豆豆"]);
    
        // 方法
        function changeInfo() {
          job.type = "后端工程师";
          job.address = "深圳";
          hobby[2] = "学习";
        }
    
        // 返回一个对象
        return {
          job,
          changeInfo,
          hobby,
        };
      },
    };
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    const 代理对象 = reactive(源对象)接收一个对象或数组,返回一个代理对象(Proxy对象)

    • reactive定义的响应式数据时“深层次的”
    • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

    这里要注意,只有改变job中的某个属性,才可以实现响应式,如果直接改job,会报警告告诉你job是只读(read-only)的。如果要改job,需要Object.assign(job, newObj);或者job= { …job, …newProperties };

    4、Vue3中响应式原理

    (1)Vue2中响应式原理

    在了解Vue3响应式原理之前,咱先来复习下vue2响应式。

    我们知道vue2中是通过Object.defineProperty()对对象类型数据进行劫持并添加getter和setter来监视数据的,对于数组是重写了更新数组的方法。复习vue2监视数据的原理

    vue2监视数据存在两个问题:

    1. 直接新增属性、删除属性, 界面不会更新。
    2. 直接通过下标修改数组, 界面不会自动更新。

    这两个问题可以通过$set$deletesplice(数组变更方法)解决

    (2)Vue3中的Proxy

    在vue3中,解决了vue2中的两个问题。

    <button @click="addSex">添加一个sex属性button>
    <button @click="deleteName">删除一个name属性button>
    
    • 1
    • 2
        function addSex() {
          // 如果是vue2,要这样写this.$set(this.person, 'sex', '女');
          person.sex = "女";
        }
        function deleteName() {
          // 如果是vue2,要这样写this.$delete(this.person, 'name');
          delete person.name;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    下面是模拟实现vue3中响应式的原理:

    <script>
      let person = {
        name: "potato",
        age: 18,
      };
      // 模拟Vue3实现响应式
      const p = new Proxy(person, {
        // 有人在读取p的某个属性时调用
        get(target, propName) {
          console.log(`有人读取了p身上的${propName}属性`);
          return target[propName];
        },
        // 有人在修改p的某个属性、或给p追加某个属性时调用
        set(target, propName, value) {
          console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`);
          target[propName] = value;
        },
        // 有人在删除p的某个属性时调用
        deleteProperty(target, propName) {
          console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`);
          return delete target[propName];
        },
      });
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    请添加图片描述

    再严谨一些,用Reflect(反射)来进行增删改查,这也是框架的做法,其实本质上没什么太大的区别,只是Reflect可以更好捕捉错误,避免一些报错吧,不然还要用try-catch去捕获。这里不用太纠结,其实就是为了让框架更加的友好(少一些报错)

    • Proxy(代理)+Reflect(反射)
    <script>
      let person = {
        name: "potato",
        age: 18,
      };
      // 模拟Vue3实现响应式
      const p = new Proxy(person, {
        // 有人在读取p的某个属性时调用
        get(target, propName) {
          console.log(`有人读取了p身上的${propName}属性`);
          return Reflect.get(target, propName);
        },
        // 有人在修改p的某个属性、或给p追加某个属性时调用
        set(target, propName, value) {
          console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`);
          Reflate.set(target, propName, value);
        },
        // 有人在删除p的某个属性时调用
        deleteProperty(target, propName) {
          console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`);
          return Reflate.deleteProperty(target, propName);
        },
      });
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    (3)Vue3响应式原理总结⭐

    对于refreactive是不一样的

    ref简单类型是通过:Object.defineProperty()的get与set,当然啊,ref定义的复杂类型是通过:reactive的Proxy

    reactive是通过Proxy来实现响应式的(上文提到了),并通过Reflect来操作源数据

    不管怎么样,总结来说,vue3中新增的就是对于复杂数据类型通过Proxy实现响应式,也就是两个点:

    1. 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性的增删、属性值的读写等。
    2. 通过Reflect(反射): 对源对象的属性进行上述操作。
    new Proxy(person, {
        // 拦截读取属性值
        get(target, propName) {
          return Reflect.get(target, propName);
        },
        // 拦截设置属性值或添加属性值
        set(target, propName, value) {
          Reflate.set(target, propName, value);
        },
        // 拦截删除属性
        deleteProperty(target, propName) {
          return Reflate.deleteProperty(target, propName);
        },
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5、reactive和ref的对比

    (1)定义数据类别不同

    • ref用于定义基本类型数据
    • reactive用于定义对象或数组类型数据
    • 备注:ref也可以定义对象或数组类型数据,它内部会自动通过reactive转为代理对象

    (2)原理不同

    • ref是通过:Object.defineProperty()getset,当然啊,复杂类型是通过reactive的Proxy
    • reactive是通过Proxy来实现响应式的(上文提到了),并通过Reflect来操作源数据

    (3)使用方式不同

    • ref定义的数据,操作时需要.value模板读取不需要.value
    • reactive定义的数据,操作和读取都不需要.value

    6、setup的两个注意点

    (1)执行时机

    setup函数的执行时机是beforeCreate之前,也就是所有生命周期的最前面,此时this是undefined,也就是说在setup中是绝对不能通过this访问组件实例的

    注意这里的beforeCreate是写在setup外面的,如果在setup里面,是没有beforeCreate和created这两个钩子的,因为setup的执行时机就相当于这两个钩子(setup会先于所有的钩子执行,把想在这两个钩子里写的代码写到steup中最前面就行了

    (2)setup的参数

    setup接收两个参数:(props,context)

    1. props是一个对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    2. context是一个对象,包含三个属性,分别是attrs、slots、emit
    • 第一个attrs相当于this.$attrs,值为对象,包含组件外部传递过来,但没有在props配置中声明的属性
    • 第二个slots相当于this.$slots,包含收到的插槽的内容。
    • 第三个emit相当于this.$emit,用来触发组件的自定义事件。
    props: ['name', 'age']
    setup(props, context) {
      console.log(props) // Proxy{name:'potato',age:18}:组件外部传递过来,且组件内部声明接收了的属性。
      console.log(context.attrs)//相当于this.$attrs
      console.log(context.slots)相当于this.$slots
      console.log(context.emit)//相当于this.$emit
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    四、计算函数和监视函数

    1、计算函数(computed)

    和vue2中功能是一样的,只不过变成了一个函数,而且要手动引入。默认写法是传一个回调

    如果我们只使用简写的话,当我们去修改计算属性值的时候,控制台会有警告,告诉我们这个属性值是只读的。

    这是因为计算属性默认是只读的,当它所依赖的值改变的时候,它自己会变。如果要想改计算属性,需要用下面的完整写法,传一个对象,里面有gettersettersetter中参数是修改后的新值

    <template>
      <h1>个人信息h1>
      姓:<input type="text" v-model="person.firstName" />
      <br />
      名:<input type="text" v-model="person.lastName" />
      <br />
      全名:<input type="text" v-model="person.fullName" />
      <br />
      <span>全名:{{ person.fullName }}span>
    template>
    
    <script>
    import { computed, reactive } from "vue";
    export default {
      setup() {
        let person = reactive({
          firstName: "张",
          lastName: "三",
        });
    
        // 计算属性-简写(没有考虑计算属性被修改的情况)
        // person.fullName = computed(function () {
        //   return person.firstName + "-" + person.lastName;
        // });
    
        // 计算属性-完整(包括读和写)
        person.fullName = computed({
          get() {
            return person.firstName + "-" + person.lastName;
          },
          set(value) {
            const nameArr = value.split("-");
            person.firstName = nameArr[0];
            person.lastName = nameArr[1];
          },
        });
    
        return {
          person,
        };
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    2、监视函数(watch)

    其实watch在vue3中和vue2中功能是一样的,就是写法不一样。

    vue2的监听函数写法点此复习vue2监听函数

    先准备一些数据吧:

    import { ref, reactive } from 'vue'
    const sum = ref(0)
    const msg = ref('hello')
    const person = reactive({
    	name: 'potato'
    	age: 18
    	job: {
    		type: 'code'
    		salary: 10
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    (1)第一个参数怎么写
    • 情况一:vue3监视ref所定义的一个响应式数据
    watch(sum, (newVal, oldVal) => {
    	console.log("sum的值变了", newVal, oldVal);
    });
    
    • 1
    • 2
    • 3

    请添加图片描述

    • 情况二:vue3监视ref所定义的多个响应式数据
    watch([sum, msg], (newVal, oldVal) => {
    	console.log("sum或msg变化了", newVal, oldVal);//new和old也是监听值构成的数组
    });
    
    • 1
    • 2
    • 3

    请添加图片描述

    • 情况三:监视reactive定义的响应式数据
      这里有两个坑,第一个是reactive定义的数据,监视时回调中无法获得oldValue!oldValue和newValue一样
      第二个坑是,监视reactive定义的数据,默认开启的deep:true,且deep不能改成false
    watch(person, (newVal, oldVal) => {
    	console.log("person变化了");
    },{immediate:true,deep:false});
    
    • 1
    • 2
    • 3
    • 情况四:监视reactive定义的响应式数据中的某个属性
      这里要注意,第一个参数必须写成箭头函数,如果直接写person.job,那么就相当于写了个死的值,这样是监视不到的。还有就是如果job是一个对象,那么默认deep是false的,如果要深度监视需要手动开启deep:true(deep配置有效)
    watch(()=>person.job,(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    • 1
    • 2
    • 3
    • 情况五:监视reactive定义的响应式数据中的某些属性
      如果这种情况的话和上面类似,不同的是第一个参数写成数组,且每个元素是箭头函数,返回的new和old值也是相对应的值构成的数组。
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    • 1
    • 2
    • 3
    (2)第一个参数到底写不写.value

    如果我们把person换成是一个ref定义的呢?那么监视的时候写不写.value

    import { ref, reactive } from 'vue'
    const sum = ref(0)
    const msg = ref('hello')
    const person = ref({
    	name: 'dantin'
    	age: 18
    	job: {
    		type: 'code'
    		salary: 10
    	}
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    答案是:要写的,因为ref对于复杂数据类型,内部是借助reactiveProxy实现响应式的,所以这么写的话就相当于是写了一个reactive定义的响应式数据,在监视时也就具有了对应的坑(见上文情况三)

    watch(person.value,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不奏效
    
    • 1
    • 2
    • 3

    除此之外还有一种办法,那就是深度监视person(它是一个RefImpl对象),这样就能监视到其中的value及更深层的变化。这是因为如果直接监视person不读取.value,那么监视的是RefImpl对象,只有其中value的地址变的时候才能监视到,value里面的东西变化是监视不到的,所以要开deep

    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    • 1
    • 2
    • 3

    那为什么简单数据类型不需要.value呢?其实和上面的情况四是一样的,如果简单数据类型直接.value,那么监视的就是一个写死的值。不.value的话,监视的是一个响应式的RefImpl对象,当里面value变化的时候是可以监视到的

    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    })
    
    • 1
    • 2
    • 3

    如果非要.value,请使用箭头函数动态读取,每次sum变化都会执行回调读取最新的值

    watch(() => sum.value,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    })
    
    • 1
    • 2
    • 3

    3、watchEffect函数

    • watch的套路是:既要指明监视的属性,也要指明监视的回调
    • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

    watchEffect有点像computed,但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

    //watchEffect的回调一上来会先执行一次
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(() => {
      const x1 = sum.value;
      const x2 = person.job.salary;
      console.log("watchEffect所指定的回调执行了");
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    腾讯云阿里云云服务器 Linux 操作系统 BT 宝塔面板快速建站教程
    IP 子网划分(VLSM)
    Vue 之 echarts 图表数据可视化的基础使用(简单绘制各种图表、地图)
    重点,一文掌握ReentrantLock加解锁原理!|原创
    JavaWeb篇_03——Tomcat下载与安装,目录结构与介绍以及Tomcat启动与关闭
    逆向USB设备共享:利用内网穿透让远程设备访问本地USB设备
    TouchGFX之画布控件
    2022.7.26 模拟赛
    PLC攻击(一):应用层攻击
    SQL必需掌握的100个重要知识点:过滤数据
  • 原文地址:https://blog.csdn.net/weixin_56498069/article/details/133101633