• 您的Vue3学习指南,请查收!!!


    您的Vue3学习指南,请查收!!!

    前言

    前端真的是太卷了,vue2.0学透,vue3.0就又来了,感觉前端更新迭代是真的快,但是又不能不学,加油卷王,卷死他们,宁死也不愿意淹没的人流之中。
    请添加图片描述

    思维导图

    在这里插入图片描述

    响应原理

    想必大家对Vue2源码了解颇深,不了解也没关系

    Vue2的响应式主要是基于Object.defineProperty方法实现

    Vue3的响应式主要是基于Es6的Proxy实现

    有什么区别呢

    首先是Vue2,大家在编写代码中一定有发现几个问题

    • Vue2对于对象新增属性是通过Vue.$set方法才能被监听到
    • 数组直接下标赋值是无法实现刷新,需要通过splice或者用this.$set方法

    造成这些现象就是因为Vue2.0响应式是基于Object.definePropety方法

    举个例子:

    	// 响应式函数
        function reactive(obj, key, value) {
            Object.defineProperty(obj, key, {
                get() {
                    console.log(`访问了${key}属性`)
                    return value
                },
                set(val) {
                    console.log(`将${key}由->${value}->设置成->${val}`)
                    if (value !== val) {
                        value = val
                    }
                }
            })
        }
        const obj = {
            name: '空白',
            age: 18
        }
        for (let key in obj) {
            reactive(obj, key, obj[key]);
        }
        obj.name = '张三' // 触发set打印将name由->空白->设置成->张三
        obj.birth = '1999.10' // 没有触发set这里birth属性没有通过defineProperty方法定义
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    从上述例子大家就应该了解,为什么Vue2的时候如果想要改变对象属性或者数组为什么用要$set去实现了吧

    接下来看一下Vue3的Proxy

        function reactive(target) {
            const handler = {
                get(target, key, receiver) {
                    console.log(`访问了${key}属性`)
                    return Reflect.get(target, key, receiver)
                },
                set(target, key, value, receiver) {
                    console.log(`将${key}由->${target[key]}->设置成->${value}`)
                    Reflect.set(target, key, value, receiver)
                    return true;
                }
            }
    
            return new Proxy(target, handler)
        }
        const obj = {
            name: '空白',
            age: 18
        }
        const newObj = reactive(obj)
        newObj.name = '张三' // 触发set打印将name由->空白->设置成->张三
        newObj.birth = '1999.10' // 触发set将birth由->undefined->设置成->1999.10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可以看到如果是通过Proxy则可以直接添加新的属性同时也能被监听到。

    Proxy知识补充(了解可以跳过)

    Proxy对象是用于创建对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

    简单说:通过Proxy我们可以对属性查找(get)、赋值(set)、枚举、函数调用的时候,自定义一些操作。

    //在以下简单的例子中,当对象中不存在属性名时,默认返回值为 37。下面的代码以此展示了 get handler 的使用场景。
    //这里就是把对象的属性查找做了处理,当获取不到属性名的时候就返回37 
    const handler = {
        get: function(obj, prop) {
            return prop in obj ? obj[prop] : 37;
        }
    };
    
    const p = new Proxy({}, handler);
    p.a = 1;
    p.b = undefined;
    
    console.log(p.a, p.b);      // 1, undefined
    console.log('c' in p, p.c); // false, 37
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    组合式Api

    这啥东西呀,又整活了
    在这里插入图片描述

    setup:替代了以前的beforeCreate和create,同时还不能访问this,那么肯定也获取不到datacomputedmethods,然而,setup返回一个对象,给计算属性、方法、生命周期钩子等使用。`。

    Reacttive

    这是什么东西,字面是意思是反应or响应,其实这个函数就是如字面一样,创建一个响应式的数据

    官方解释: 返回对象的响应式副本

    这里也许有人就不太清楚,那么我们这里来一个例子你就懂了

    import { reactive } from 'vue'
    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      },
      setup() {
        const obj = reactive({
          name: '张三',
          age: '18'
        });
        // 其实reactive的方法就是帮你把数据转化成响应式的。什么是响应式? 例如:目前name为张三
        //那么调用setPerson方法,网页上看见会立即更新,注意赋值的方式,因为vue3是使用Proxy方法所以直接赋值属性也是会更新的
        const setPerson = () => {
          obj.name = '李四'; // 注意 这里是直接设置值obj.name 如果是vue而应该用vue.$set
          obj.age = '24'
        }
        return {
          obj,
          setPerson
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    Ref

    refreactive函数类似,只是说ref一般是传一个值作为参数,返回一个响应式ref对象,另外需要注意的是ref若果传的值是对象则会被reactive函数处理,但是还是有些许不同

    例如:

    const count = ref(0);
    const addCount = () => {
    	count.value++
    }
    
    • 1
    • 2
    • 3
    • 4

    返回Ref对象,值为0同时是响应式的

    在这里插入图片描述

    reactiveref对象

    	// ref创建对象
        const person = ref({
          name: '张三',
          age: '18'
        })
        // reacttive创建对象
        const obj = reactive({
          name: '张三',
          age: '18'
        });
        console.log(person);
        console.log(obj);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    可以看到如果ref传的值是一个对象,其实内部value本质与reactive生成的对象是一致的

    toRefs

    toRefs:用于解构响应式对象里的属性,同时让解构的属性还保留响应式

    举个例子:现在我们创建一个person对象,里面有nameage,此时我如果想把person里的属性解构出来,那么我直接把它拿出来使用会有什么效果?

    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      },
      setup() {
        // ref创建对象
        const person = reactive({
          name: '张三',
          age: '18'
        })
        const setPerson = () => {
          person.name = '李四';
          person.age = '24'
        }
        return {
          ...person,
          setPerson,
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    请添加图片描述

    可以看到如果是通过直接解构的方式,响应式的效果已经消失了

    我们就需要通过toRefsreactive的属性解构出来

    import { reactive, toRefs } from 'vue'
    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      },
      setup() {
        // ref创建对象
        const person = reactive({
          name: '张三',
          age: '18'
        })
        const { name, age } = toRefs(person) // 解构
        const setPerson = () => {
          person.name = '李四';
          person.age = '24'
        }
        return {
          name,
          age,
          setPerson,
        }
      }
    }
    
    
    • 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

    1

    可以看到此时我们再去修改person的属性已经实时响应到页面上了

    watch

    watchvue2.0的时候类似,但是可以同时监听多个,这里不过多赘述,大家知道怎么用就可以了

    监听单一源

    // 侦听一个 getter
    const state = reactive({ count: 0 })
    watch(
      () => state.count,
      (count, prevCount) => {
        /* ... */
      }
    )
    
    // 直接侦听一个 ref
    const count = ref(0)
    watch(count, (count, prevCount) => {
      /* ... */
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    监听多个源

    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
      /* ... */
    })
    
    • 1
    • 2
    • 3

    watchEffect

    watchEffect:也是用来监听值变化后执行一系列操作,但是最主要的区别是watchEffect不用指定监听源,会自动收集,意味着watchEffect里的任意值改变都会触发

    来做个实验吧

    import { reactive, watchEffect, toRefs } from 'vue'
    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      },
      setup() {
        // ref创建对象
        const person = reactive({
          name: '张三',
          age: '18'
        })
        watchEffect(() => {
          console.log('person对象改变了', person.name, person.age);
        })
        const setName = () => {
          person.name = '李四'
        }
        const setAge = () => {
          person.age = '23'
        }
        return {
          ...toRefs(person),
          setName,
          setAge
        }
      }
    }
    
    • 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

    请添加图片描述

    可以看到我们此时并没有指定对象,但是当里面的属性改变了,watchEffect的回调函数就自动执行了

    computed

    计算属性:与vue2.0基本没什么区别。返回值变成了返回不可变的ref对象

    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
    
    console.log(plusOne.value) // 2
    
    plusOne.value++ // 错误 不能去修改computed返回的对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: val => {
        count.value = val - 1
      }
    })
    
    plusOne.value = 1
    console.log(count.value) // 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    生命周期

    生命周期其他也没有很大的改变主要是一些生命周期钩子的命名变了,在加上setup

    img

    在下图中可以看到官方推荐用setup替代beforeCreate和create,同时setup的执行是在这两个钩子函数之前的,另外我们在vue3中照样可以使用beforeCreate和create这两个方法

    在这里插入图片描述

    Fragment

    不得不说在Vue3中有一点还是挺舒服的,那就是Fragment这个概念其实很简单,就是在Vue2.0templete其实是只能有一个根元素的而在vue3中我们可以有多个根元素

    举个例子

    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结果:正常输出

    在这里插入图片描述

    teleport(传送门)

    teleport:也是vue3中的创新,主要功能是:将某个元素渲染到其他指定位置,那不是多此一举?有什么用?

    不知道大家有没有遇到这样的场景

    你要显示某个结果层级不够被弹窗挡住

    那么一般这种调用在组件内被一层层覆盖,如果我们能将元素挪去与Vue的根元素同级那不是美滋滋。

    于是就诞生了teleport现在我们来实践一下

    首先在index.html声明元素,并且赋予对应的id

    image-20220803211259858

    接着使用teleport把需要传送的元素包裹,然后就要开始施法了

    在这里插入图片描述

    可以从图片看到我们此时的代码是声明在HelloWorld组件中,那么在浏览器中会渲染到哪里?

    在这里插入图片描述

    app根元素同级,并不是在组件中,那么到这里我还有个疑问,此时的组件拿的数据是哪里的数据?

    HelloWorld这个层级的吗? 我们来实验一下,通过点击按钮改变HelloWorld组件中的data

    请添加图片描述

    从动图中我们可以看到,点击按钮后组件中的值确实改变了,则可以断定vue其实只是把元素挪到指定地方,获取的数据还有逻辑还是从原有的地方获取

    Tree-shaking

    Tree-shaking:俗称摇树,功能简单说就是让打包的体积更小,去掉不需要的东西,这个其实是属于webpack的东西,有兴趣的同学可以去看看webpack的优化tree-shaking,这里建议深究,毕竟要把时间花在刀刃上

    Suspense(异步组件)

    Suspense:目前这个组件还是实验性而已,在以后的版本中有可能被删除也有可能保留,目前只需要做了解即可

    异步组件,主要是为了处理异步加载时的显示,平常我们在请求还未拿到数据时经常是显示loading

    23

    为了处理现实loading在vue2的时候我们一般是使用v-if判断

    Vue3中提供异步组件Suspense可以帮助我们解决这个问题

    
    
    
    
    • 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

    ate #default>