Vue3源码之setup

前言

组合式API是Vue3一个非常亮眼的功能,它的出现是为了解决复杂组件代码的可复用性、可阅读性和可理解性的问题。当说起组合式API,实际上最先想起的就是setup选项,组件中的setup选项就是组合式API的基础,它充当组合API的入口点。区别于Vue2中使用选项组织代码,组合式实际上是对在setup中使用API这种方式的一种描述,如果你愿意实际上你可以在setup选项中完成绝大多数的功能逻辑,例如注册生命周期、定义computed和watch等等。

本文旨在梳理setup具体的执行逻辑,加深对其的理解和使用(Vue 3.1.1版本)。

setup

在之前mount挂载的文章中就提到setup选项的处理是在setupComponent函数,而该函数是在mountComponent中被调用。setupComponent函数的具体处理逻辑如下:

function setupComponent(instance, isSSR = false) {
	const { props, children } = instance.vnode;
    const isStateful = isStatefulComponent(instance);
    initProps(instance, props, isStateful, isSSR);
    initSlots(instance, children);
    const setupResult = isStateful
    	? setupStatefulComponent(instance, isSSR)
        : undefined;
	...
}

setupComponent函数的的逻辑很清晰,主要有如下几点:

  • isStatefulComponent函数实际上就是判断虚拟节点shapeFlag的值是否是4,即普通有状态的组件
  • initProps 和 initSlots
  • 有状态的组件会调用setupStatefulComponent函数
initProps

initProps的主要逻辑如下:

function initProps(instance, rawProps, isStateful, isSSR = false) {
	const props = {};
    const attrs = {};
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = Object.create(null);
    setFullProps(instance, rawProps, props, attrs);
    ...
    if (isStateful) {
     	// stateful
        instance.props = isSSR ? props : shallowReactive(props);
    }
    instance.attrs = attrs;
}

实际上就是处理props和attrs,而props会通过shallowReactive进行浅层代理,这决定了使用需要props只代理了自身属性。其中attrs的处理是不同于Vue2,Vue3 attrs是Vue2中$attrs和$listeners的集合。

setupStatefulComponent

该函数的主要关注的逻辑如下:

function setupStatefulComponent(instance, isSSR) {
	const Component = instance.type;
	...
	instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers));
	const { setup } = Component;
	if (setup) {
		instance.setupContext = setup.length > 1
			? createSetupContext(instance)
			: null
		const setupContext = instance.setupContext;
		const setupResult = callWithErrorHandling(
			setup,
			instance,
			0,
			[shallowReadonly(instance.props) , setupContext]
		);
		if (isPromise(setupResult)) {
        	instance.asyncDep = setupResult;
        } else {
        	handleSetupResult(instance, setupResult, isSSR);
        }
	}
}

setup函数是通过callWithErrorHandling函数来执行的(需要注意setup函数中this值的指向,callWithErrorHandling函数仅仅是调用而已并没有显式绑定this,所以this指向全局对象即window),其中参数是:shallowReadonly(instance.props) 、setupContext,其中props设置为浅层只读,而setupContext提供下面三个属性的获取:

function createSetupContext(instance) {
	...
	return Object.freeze({
    	get attrs() {
        	return new Proxy(instance.attrs, attrHandlers);
        },
        get slots() {
        	return shallowReadonly(instance.slots);
        },
        get emit() {
        	return (event, ...args) => instance.emit(event, ...args);
        },
        ...
    });
}

而针对于setup函数的返回值,实际上存在如下几种类型:

  • 返回值是Promise对象,就赋值给instance.asyncDeps
  • 返回值是function,就赋值给instance.render
  • 返回值是Object,就赋值给instance.setupState,需要注意的是会使用proxyRefs做处理。实际上proxyRefs就是返回一个代理对象,主要是针对ref对象的特殊处理,在模板中可以直接使用而无需调用ref对象的value

setup函数的返回值可以直接在模板中使用,背后的实现逻辑实际上与mount挂载返回值类型以及template构建的render的上下文参数的有关,mount挂载返回的组件实例实际上就是一个Proxy对象,实际上组件中使用this就是组件实例,即instance.proxy:

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

而template构建的render函数中上下文参数最后也是调用PublicInstanceProxyHandlers中定义的拦截方法。

PublicInstanceProxyHandlers

这里值关心其拦截的get操作,对于属性获取的相关主要逻辑如下:

const PublicInstanceProxyHandlers = {
	get({ _: instance }, key) {
    	...
    	if (key[0] !== '$') {
    		const n = accessCache[key];
            if (n !== undefined) {
            	switch (n) {
                	case 0 /* SETUP */:
                    	return setupState[key];
                    case 1 /* DATA */:
                    	return data[key];
                    case 3 /* CONTEXT */:
                    	return ctx[key];
                    case 2 /* PROPS */:
                    	return props[key];
                    // default: just fallthrough
                }
            }
            ...
        }        
	}
}

实际上对于组件实例上获取对应属性,其相关get拦截中实际上存在一种缓存机制,对于非$开头的属性第二次时会从指定数据源中获取,分别是:

  • setupState:setup函数的返回值
  • data:data选项中的数据
  • ctx:组件实例都会有一个上下文,方法、$开头的内部属性等都会注册到这里
  • props

总结

setup函数的执行逻辑相对来说是非常清晰的,其函数值存在不同类型的返回值,而针对不同类型的返回会有不同的处理,返回值类型支持:promise、function、object。虽然setup比较特殊,但是可以直接访问setup函数返回的值,不需要特殊调用,这一起都是源于组件实例是Proxy对象,其内部get拦截做了特殊的处理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3的setup代码是在组件中定义的一个函数,用于替代Vue2中的created、mounted等生命周期钩子函数。该函数在组件实例创建之前执行,并且接收两个参数:props和context。在setup函数中,可以进行一些初始化工作,例如设置响应式数据、定义方法等。 下面是一个示例的Vue3组件的setup代码: ```javascript import { reactive } from 'vue'; export default { setup(props, context) { // 响应式数据 const data = reactive({ message: 'Hello, Vue3!' }); // 方法 function showMessage() { console.log(data.message); } // 在模板中使用的变量和方法可以通过返回对象进行暴露 return { data, showMessage }; } } ``` 在上面的代码中,我们使用`reactive`函数创建了一个响应式的`data`对象,并定义了一个`showMessage`方法。最后,通过返回一个对象,将`data`和`showMessage`暴露给模板中使用。 需要注意的是,由于setup函数在组件实例创建之前执行,因此无法直接访问实例对象(`this`),也无法访问组件选项如`data`、`computed`、`methods`等。此外,setup函数的`this`也未绑定到组件实例上。 总结起来,Vue3的setup代码是用于在组件实例创建之前进行初始化工作的函数,在其中可以定义响应式数据和方法,并通过返回对象将它们暴露给模板中使用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Vue3源码 第四篇-Vue3 setup](https://blog.csdn.net/qq_26626113/article/details/120657470)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值