初识Vue3(1)

118 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

虽然现在是开始用了一段时间的Vue3了,但是也没真正的去系统了解Vue3和Vue2的差别。这导致我在开发过程中老是遇见问题时就去百度,同时也踩了很多的坑。所以我认为我应该认真去瞧瞧Vue3了。

Vue3的setup函数

在vue3中,它不像Vue2那样需要写一个又一个的配置项了,他是你需要用什么,就去取什么,使用的是组合API的方式。所以我们需要对数据的定义、方法的定义、生命周期的使用、watch和computed的使用等,都不必单独写在外面了,而是全部写在setup函数里面。

setup函数有几个注意点:

  • 数据和方法定义了,如果需要在页面中使用,必须得将它返回(一般都是在setup函数里面返回一个对象,对面里就是页面要使用的属性和方法)。

  • Vue3调用setup函数的时间很早,在beforeCreate钩子函数之前就已经调用了,所以setup的this为undefined。

  • setup有两个参数

    • 第一个参数是props,如果我们想在setup里面使用父组件传递给本组件的数据,就得在setup里面使用这个参数。
    • 第二个参数是context,代表的是上下文对象,里面有3个方法,一个是attrs,一个是slots、一个是emit。(感觉一般常用的就是emit吧?)

此外,Vue3.2更新了setup的语法糖。就不用再去声明setup函数并return一个对象了。但是setup语法糖会有一些其他的不同,比如父组件或其他组件获取当前组件时,是获取不到定义的数据或者方法的,需要expose出去,且setup的参数props以及emit需要额外的定义。

setup语法糖

新增的API:

  • defineProps:接收父组件中传来的props
  • defineEmits:接收父组件中的方法
  • defineExpose:子组件暴露属性,父组件中可以拿到
  • useAttrs:接收父组件中的属性
  • useSlots:接收父组件中的插槽
<script lang="ts" setup> 
    import {defineProps, useSlots, useAttrs,ref} from 'vue'; 
    const slots = useSlots() 
    const attrs = useAttrs()
    const value = ref(10)
    defineProps({ num:{ type:Number, default:NaN } }) 
    const emit = defineEmits(["change"]); // 声明触发事件 change
    const sonClick = () =>{ emit('change' , value) }
    //暴露出子组件中的属性 
    defineExpose({ value })
</script>

ref和reactive

vue3的响应式原始数据类型用的还是Object.defineProperty,引用对象类型用的是Proxy。在Vue2中我们将数据直接写在data函数里面,系统会自动给我们进行数据代理、数据劫持,然后成为响应式。而Vue3我们在setup里面声明的变量,在返回之后,却并不是响应式的,这需要我们自己手动进行响应式(不是手动写响应式,就是手动调用官方的方法,让其变为响应式...)。

  • 如果是原始数据类型,我们就用ref函数对其进行包装。
  • 如果是引用数据类型,我们就用reactive对其进行包装。
<script>
    import {ref,reactive} from 'vue'
    export default {
        setup() {
            let num = ref(0);
            const person = reactive({
                name:'ly',
                age:18
            })
            return {
                num,
                person
            }
        }
    }
</script>

但是需要注意的是,用ref包装的数据类型,返回的是一个对象,这个对象身上有一个属性value,如果我们要在setup函数里面使用or修改这个原始数据,我们就必须使用xxx.value才可以。至于为什么要这样呢?是因为ref内部采用的是Object.defineProperty,而ref为什么要出现,就是因为reactive只能将引用类型的进行响应式,而不能对基础数据类型进行响应式,所以才有的ref专门用于处理基础数据类型。而Object.defineProperty它也只能对对象进行数据的劫持,所以vue就将基础数据类型给包裹了一层。

...
let num = ref(0);
const addNum = () => num.value++;
...

源码逻辑是这样的:

function ref(value) {
    return createRef(value);   //将普通类型变成一个对象  因为普通类型不能数据劫持
}
​
function createRef(rawValue,shallow=false) {
    return new RefImpl(rawValue,shallow)
}
​
class RefImpl {
    public _value;   //声明一个value属性
    public _v_isRef = true;  //产生的实例会被添加  表面这是ref属性
    constructor(public rowValue,public shallow) {  // public表明会放在实例上
        this._value =shallow? rowValue:convert(rowValue); //如果是深度就把里面都变成响应式的,也就是说当是对象或数组时,这里就调用reactive的方法。
    }
    get value() { //这里使用了Object.defineProperty进行了数据劫持与依赖收集
        track(this,TrackOpTypes.GET,'value');
        return this._value;
    }
    set value(newValue) { //这里进行了数据的比对,如果新旧数据不一致就更新数据,然后更新视图
        if(hasChanged(newValue,this.rowValue)) {
            this._value = this.shallow? newValue:convert(rowValue);
            this.rowValue = newValue;
            trigger(this,TriggerOpType.SET,'value',newValue)
        }
    }
}

reactive包装的引用数据类型,返回的是一个proxy实例对象。

而关于reactive的讲解,我推荐一个大佬的,写得非常好!

链接:juejin.cn/post/700612…

toRef

可以把一个对象的值转化成ref类型。

我们什么时候会用到它呢?答案就是当我们想取出一个对象(响应式的)的某个属性时,我们直接赋值后,这个值不会是响应式的。

比如:

setup() {
    const person = reactive({
        name:'ly',
        age:18
    })
    let name = person.name;
    return {
        person,
        name
    }
}

但是我们又想他是响应式的咋办?最笨的方法就是使用ref去包裹一层。

let name = ref(person.name);

但是,这就有一个问题了,那就是name和person.name就完全不相关了,属于是两个东西了,以后修改person.name不会导致name的变化,修改name不会导致person.name的变化。所以这就有了toRef。

let age = toRef(person,'age');
function ageAdd() {
    age++; //person.age++
}

现在就可以实现两者的共同响应更新了。

它的大致原理:

function toRef(target,key) {
    return new ObjectRefImpl(target,key); 
}
​
class ObjectRefImpl {
    public _v_isRef = true;
    constructor(public target,public key) {
        
    }
    get value() {
        return this.target[this.key]
    }
    set value(newValue) {
        this.target[this.key] = newValue;
    }
}

所以它内部最终对于对象或数组属性还是会去触发proxy,toRef只是一个中间站。

toRefs

上面我们说了toRef的作用,但是它有一个缺点,那就是它只能一次将对象的一个属性变成ref,如果我们想将多个属性变成ref类型咋办呢?于是就有了toRefs。

setup(){
    let data=reactive({
        state:'normal',
        pageSize:20,
        currentPage:1,
        dialogShow:false
    })
    const {state,pageSize} = {...toRefs(data)};
    return {
        ...toRefs(data);   //对对象进行解构,返回的就是state、pageSize等对象了
    }
}

它的大致原理就是底层调用了toRef。

function toRefs(object) {
    const ret = isArray(object)?[]:{};
    for(let key in object) {
        ret[key] = toRef(object,key)
    }
    return ret;
}

相当于Vue自己写了个对象解构方法。

总的来说,vue3的新改动还是很多的,下次再更新更新watch相关的用法和坑。