前言
这是vue3系列源码的第四章,使用的vue3版本是3.2.45
。
推荐
背景
在上一篇文章里,我们探索了vue3的源码中,加载页面部分的源码。纯纯的页面,没有涉及到js
代码,那么我们今天想看看,js
代码到底是什么时候执行的。
我们今天说的js
代码不包括各个周期钩子和副作用函数,只是纯纯的js
代码。
前置
我们来看一下我们这次的App.vue
组件长什么样子。
js
<template>
<div>{{ aa }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const aa = ref(11)
console.log('setup', aa.value)
onMounted(() => {
console.log('onMounted',aa.value)
})
</script>
这里我们只看js部分,其实div是不需要的,不影响。
下面我们就来看看到底会在源码执行的哪一步,打印我们的内容。
在上一篇mount都发生了什么文章中的mountComponent
函数中,我们提到过setupComponent
这个函数,当时我们没有详细的去说,那么今天,我们讲深入的去了解这个函数。
setupComponent
js
function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
这里的参数很简单,就是组件的实例。
整个函数看似内容不多。我们可以一个一个看下去。
首先是isStateful
,这里其实就是一个与的运算,最终得到的是4,在下面的判断中代表true。
接着是initProps
和initSlots
,但是我们这里并没有。而且也不是我们这一章的重点。我们将在后面的章节专门深入。
最后到了setupStatefulComponent
,前面都不是重点,那么他就是绝对的重点。
setupStatefulComponent
js
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 0. create render proxy property access cache
instance.accessCache = Object.create(null)
// 1. create public instance / render proxy
// also mark it raw so it's never observed
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
// 2. call setup()
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
setCurrentInstance(instance)
pauseTracking()
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
if (isPromise(setupResult)) {
...
} else {
handleSetupResult(instance, setupResult, isSSR)
}
} else {
...
}
}
参数还是组件的实例。
这段代码的重点在:
- callWithErrorHandling(setup, instance...),就是在这里,我们的setup的代码执行了。
handleSetupResult
setup
我们先看一下setup的执行。
回顾我们的代码段:
js
import { ref, onMounted } from 'vue'
const aa = ref(11)
console.log('setup', aa.value)
onMounted(() => {
console.log('onMounted',aa.value)
})
在setup的执行里,进行了以下步骤:
- 调用了
ref
函数创建了ref对象aa
- 执行到
console.log('setup', aa.value)
,触发了aa
对象的get - 执行了打印操作,就是在这里,打印了
setup 11
- 调用了
createHook
函数,向该组件的onMounted
钩子注入定义的函数
由此可见,我们setup中的内容其实就是在这里执行了。定义变量和console以及一般的js
代码都会在这里得到执行。
我们还发现了,定义在钩子里面的代码,也就是传给钩子的回调函数也是在这里进行了注册,然后等待相应的时机再执行。
这里加了console.log('onMounted',aa.value)
纯粹是为了和console.log('setup', aa.value)
执行上的不同,关于钩子函数的执行,不在本章讨论,我们将会在后面,详细看看钩子回调函数的执行。
这里其实还涉及到了ref
,我们也将会在后面的章节里面进行分析。
handleSetupResult
js
function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {
...
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
} else if (__DEV__ && setupResult !== undefined) {
...
}
finishComponentSetup(instance, isSSR)
}
我们先看一下参数:
- instance,组件实例
- setupResult, 就是上一步
setup
执行的结果,是一个对象,具体的内容见下图
这个函数其实和标题setup
内容的执行关系不太大了,因为我们想要的已经知道了,这里只是顺带提一下proxyRefs
。
通过proxyRefs
方法对setupResult
对象进行了代理,这样我们在组件template
中访问aa
的时候就不用写aa.value
了,而是直接写aa
就可以了。
总结
我们虽然只探索了setup的执行,但是其实也顺带着了解了一下ref
和生命周期钩子。
从这章开始,我们将逐渐的去了解vue3的那些API。