Vue3 Composition-API

2,142 阅读6分钟

Vue3 Composition-API

本文将简要介绍一下当前Vue3组合式API的使用方式及意义。

当然,该调侃的时候还是要调侃一下的,嘿嘿

1.组件状态

以2.x的使用习惯为例,我们最关心的就是双向数据绑定的使用形式:

<template>
<button @click="increment">
    当前是: {{ state.count }}, 双倍是: {{ state.double }}
</button>
</template>
<script>
import {
    computed,
    reactive
} from 'vue'
export default {
    setup() {
        const state = reactive({
            count: 1,
            double: computed(() => state.count * 2)
        })
        function increment() {
            state.count++;
        }
        return {
            state,
            increment
        }
    }
}
</script>

reactive,接收一个普通对象然后返回该对象的响应式代理,等同于2.x中的Vue.observable()

响应式转换是“深层的”:会影响对象内部所有嵌套属性。基于Proxy的实现,返回的代理对象不等于原始对象。在使用时应尽量使用代理对象而避免依赖原始对象。

Vue响应式系统的精髓:当在组件中从data()返回一个对象,,内部实质上是通过调用reactive()使其变为响应式的。

computed, 传入一个getter函数,返回一个不可手动修改的ref对象;

computed的另一种使用方式是,传入一个带有get和set的函数对象,创建一个可以手动修改的计算状态;

const double = computed({
    get: () => count.value,
    set: (val) => {
        count.value = val * 2
    }
})

watchEffect对执行过程中用到的响应式状态作为依赖进行跟踪(与2.x中的watch选项类似,但是它不需要把被依赖的数据源和副作用回调分开),并在依赖变更时重新运行该函数。当组件的setup()或者生命周期钩子被调用时,watchEffect会被链接到该组件的生命周期,并在组件卸载自动停止

新的ref,接受一个参数值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性,即.value

<template>
<button @click="increment">
    数字增加
</button>
<p>{{num}}</p>
</template>
<script>
import {
    ref
} from 'vue'
export default {
    setup() {
        const num = ref(0);
        function increment() {
            num.value ++;
        }
        return {
            increment,
            num
        }
    }
}
</script>

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

readonly,传入一个对象或者ref,返回一个原始对象的只读代理。即使是深层对象,内部的任何属性也都是只读的。

const original = reactive({count: 0});
const only = readonly(original);
watchEffect(() => { //依赖追踪
    console.log(only.count);
})
original.count ++; //这里的修改会触发only的监测
only.count ++;  //无法修改并发出警告

至此简介已经覆盖了组件的纯状态层面:响应式状态、计算状态和用户输入时的状态变更。接下来将介绍生命周期及组合式API。

2.生命周期钩子

import {
    onMounted, onUpdated, onUnmounted
} from 'vue'
setup() {
  //...
        onMounted(() => {
            console.log('组件已经被挂载了!')
        })
     onUpdated(() => {
        console.log('组件已经更新了!')
     })
     onUnmounted(() => {
        console.log('组件已经被卸载了!')
     })
        //...
}

如上例所示,生命周期钩子函数只能注册在setup函数中,因为它们依赖于内部的全局状态来定位当前组件实例(即正在调用setup()的组件实例),不在当前组件下调用会抛出错误。

组件实例上下文都是在生命周期钩子同步执行期间设置的,所以在卸载组件时同步创建的侦听器和计算状态也会被删除。

除上例所示外,还有钩子:onBeforeMount、onBeforeUpdate、onBeforeUnmount、onErrorCaptured以及新增的钩子onRenderTracked、onRenderTriggered。

3.组合式API VS 选项式API

有组织的代码最终让代码更可读,更易于理解。在组件中看到的是“如何处理这个X、Y和Z”,而不再是“这个组件有这些data、这些property、这些methods”,即更加关心“这个组件是要干什么的”。基于选项的API写出来的代码自然不能很好地表述出组件的功能。

以下图为例,当以选项式API进行开发时,通过阅读选项中的代码梳理出各个逻辑是非常困难的,因为与逻辑相关的代码都分散在各处。这种碎片化开发使得后期阅读和维护变得相当困难,选项的强行分离为逻辑的理解抬高了门槛,我们不得不在各个代码块之间来回跳转,以找到相关代码。

相反,如果能把相同的逻辑点代码放在一起的话,那将是再好不过的事情了。这正是组合式API要做的事情。这种模式让该组件的逻辑点最终成为了良好的解耦函数:每个逻辑点代码块都被组合进一个函数中,大大减少了来回跳转的情况。你还可以把这些组合函数折叠起来,更加易于浏览:

除了提取逻辑外,另一个变化就是this的引用。

setup()中的this与2.x中的this完全不同,同时在setup()和2.x中使用this将会造成混乱。这里就需要再介绍一下setup函数了:

  • 创建组件实例时,先初始化props,紧跟着就要调用setup函数,并且将在beforeCreate之前被调用;

  • setup返回一个对象,对象的属性将会被合并到组件模板的上下文中;

  • setup的第一个参数,就是props;

  • setup的第二个参数,提供了上下文对象,并且从2.x中选择性暴露了一些属性。

export default {
    props: {
        namestring,
        agenumber
    },
    setup(prop,context) {
        watchEffect(() => {
            console.log(`My name is ${props.name},I'm ${props.age} years old.`)
        });
        context.attrs
        context.slots
        context.emit
    }
}

当提取逻辑点时,逻辑方法也可接收参数props和context:

const checkUsername = (props, context) => {
  onMounted(() => {
    console.log(props)
    context.emit('event', 'payload')
  })
}
export default {
  setup (props, context) {
    checkUsername(props, context)
  }
}

4.最后

希望本文能帮助更好地了解Composition API将如何改变我们的编码方式,在后续的开发活动中,提高代码的阅读性,提高内聚耦合度,毕竟这才是成熟的程序员要做的。