[vue3] vue3 setup函数

cnblogs 2024-08-25 17:41:00 阅读 99

本文介绍了Vue3中的setup函数的运行原理,并介绍了setup与选项式API共存时的处理原则。

从语法上看,Composition API 提供了一个 setup 启动函数作为逻辑组织的入口,提供了响应式 API,提供了生命周期函数以及依赖注入的接口,通过调用函数来声明一个组件。

Options API

    <li>选项式 API 在 props、data、methods、computed 等选项中定义变量;
  • 在组件初始化阶段,Vue.js 内部处理这些 options,把定义的变量添加到组件实例上;
  • 等模板编译成 render 函数的时候,内部通过 <code>with(this){} 的语法去访问在组件实例中的变量。li>

在 Vue3 中,这两种 API 能够同时使用,但执行的优先级不同,建议只使用其中一种。

  • Options API 适合小型简单的组件;
  • Composition API 适合大型复杂、需要拆分逻辑的组件。

组件初始化

在 Vue3 中,render函数可以访问到 setup 函数返回的数据,这是怎么实现的呢?

组件的渲染流程是:创建vnode、渲染vnode、生成DOM。

其中渲染vnode就是在挂载(或更新)组件,通过 patch 函数对不同类型的 vnode 进行挂载或更新。

setup 函数只在首次挂载的流程中调用,因此这里主要研究挂载的流程。

通过 patch 函数内部的调用链通过 mountComponent 函数进行组件挂载。

mountComponent

const mountComponent = (

initialVNode,

container,

anchor,

parentComponent,

parentSuspense,

namespace: ElementNamespace,

optimized,

) => {

// 创建组件实例

const instance: ComponentInternalInstance =

(initialVNode.component = createComponentInstance(

initialVNode,

parentComponent,

parentSuspense,

))

// 设置组件实例:props, slots ...

setupComponent(instance)

// 设置并运行带副作用的渲染函数

setupRenderEffect(

instance,

initialVNode,

container,

anchor,

parentSuspense,

namespace,

optimized,

)

}

mountComponent 函数内部主要执行了三个函数:

  • createComponentInstance:【工厂模式】内部通过对象字面量创建一个组件实例对象并返回,对象包含许多属性,例如:
    • effect,update,job:与副作用和更新逻辑相关;
    • components、directives:局部组件与局部指令;
    • ctx、data、props:state相关;
    • bc、c、bm、m、bu:声明周期相关,源代码中用首字母命名,bc 是 beforeCreate;
  • setupComponent:
    1. 判断组件是否有状态;
    2. 初始化props
    3. 初始化slots
    4. 如果有状态,则调用 setupStatefulComponent 进行组件实例设置,内部调用了 setup 函数
  • setupRenderEffect:创建一个与更新相关的副作用,再包装成 Job 对象。这个副作用内部会调用生命周期 hook 。

这篇文章主要介绍setup,下面主要内容是执行了setup函数的 setupStatefulComponent 函数。

setupStatefulComponent

创建代理

这个函数创建了渲染上下文代理,创建代理的目的是让访问数据更加简便。

例如在 Vue2 中 props 的数据实际存储在 this._props 上,而 data 的数据则存储在 this._data 上,在组件方法中可以通过 this.msg 访问到 this._data.msg ,就是因为使用了代理。

而在 Vue3 中,不同的状态被存储在了组件实例的 setupState、ctx、data、props中。通过创建一个代理,渲染函数可以在一个对象上进行读写操作,再由代理将读写操作分发给不同的状态对象。

// 0. create render proxy property access cache

instance.accessCache = Object.create(null)

// 1. create public instance / render proxy

instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)

函数内部还创建了一个 accessCache 对象,它是一个 key 到 state类型 的映射,避免每次读取一个 key 都要去判断这个 key 属于哪一种状态。

PublicInstanceProxyHandlers 内部关于 accessCache 的快速命中代码:

const n = accessCache![key]

if (n !== undefined) {

switch (n) {

case AccessTypes.SETUP:

return setupState[key]

case AccessTypes.DATA:

return data[key]

case AccessTypes.CONTEXT:

return ctx[key]

case AccessTypes.PROPS:

return props![key]

// default: just fallthrough

}

}

如果没能命中缓存,内部只能通过 hasOwn 方法一个一个去判断状态对象上是否存在查询的 key。hasOwn 是这一过程中的开销大头。

判断的顺序很重要,当不同的状态对象有着相同名称的属性,那么优先应用那个先判断的类型。

get拦截(在没有命中缓存的情况下)的判断顺序是 setup、data、props、context。

这意味着当我们混用 setup函数 和 选项式 API 时,同名响应式变量会命中 setup 中声明的变量。

下面的代码会在界面上显示“from setup”。

<template>

<p>{{ msg }}</p>

</template>

<script>

import { ref } from 'vue';

export default{

data() {

return {

msg: 'from data'

}

},

setup(){

const msg = ref('from setup');

return {

msg

}

}

}

</script>

上面说的都是对get的拦截,对于set的拦截简要介绍如下:

  • key 的判断顺序和 get 一样;
  • 在开发环境中对props的修改操作进行警告。
调用setup

setupStatefulComponent 函数在创建了这个上下文代理之后,就调用了 setup 函数

大致流程如下:

  1. 判断是否有 setup 函数;

  2. 如果setup函数参数列表长度大于1,则调用 createSetupContext 函数创建 setup 上下文对象;

    setup函数的参数如下,第二个参数是一个上下文对象。如果用户(前端程序员)编写setup的时候使用了第二个参数,那么Vue在执行setup函数之前就要把这个上下文对象准备好。

    setup(props, { attrs, slots, emit, expose }) {

    ...

    }

  3. 执行 setup 函数获取返回的结果;

    执行 setup 函数是通过 callWithErrorHandling 间接调用的。其实内部不复杂,有参数则携带参数执行,用 try/catch 捕获错误。

    export function callWithErrorHandling(

    fn: Function,

    instance: ComponentInternalInstance | null | undefined,

    type: ErrorTypes,

    args?: unknown[],

    ): any {

    try {

    return args ? fn(...args) : fn()

    } catch (err) {

    handleError(err, instance, type)

    }

    }

  4. 处理 setup 返回的结果。

    setup 函数的返回值有两种类型:数据对象 和 渲染函数。

    • 如果是渲染函数,则绑定到 instance.render 上;

      instance.render = setupResult as InternalRenderFunction

    • 如果是数据对象,则先进行响应式包装,再绑定到 instance.setupState 上。

      instance.setupState = proxyRefs(setupResult)

至此,setup函数执行完成了,相关的数据也都绑定到了组件实例上。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。