vue3源码学习4-setup启动函数
vue3的单文件代码如下:
<template>
<div>
<div>{{msg}}</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const msg = ref('哈喽!')
return {
msg
}
}
}
</script>
模板访问的msg变量包含在Composition API 入口的setup函数中,他们之间的联系是怎么实现的?
packages/runtime-core/src/renderer.ts
我们之前看过组件的渲染流程:创建vnode->渲染vnode->DOM,渲染vode的过程主要是挂载组件:
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
// 创建组件实例
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 设置组件实例
setupComponent(instance)
// 设置并运行带副作用的渲染函数
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
这一小节,我们只看创建组件实例和设置组件实例,首先创建组件实例createComponentInstance方法在packages/runtime-core/src/component.ts文件中:
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
// inherit parent app context - or - if root, adopt from root vnode
// 继承父组件实例上的 appContext, 如果是根组件,则直接从根vnode中获取
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
// 组件唯一 id
uid: uid++,
// 组件 vnode
vnode,
// vnode节点类型
type,
// 父组件实例
parent,
// app上下文
appContext,
// 根组件实例
root: null!, // to be immediately set
// 新的组件 vnode
next: null,
// 子节点 vnode
subTree: null!, // will be set synchronously right after creation
// 响应式相关对象
effect: null!,
// 带副作用的更新函数
update: null!, // will be set synchronously right after creation
// 作用域
scope: new EffectScope(true /* detached */),
// 渲染函数
render: null,
// 渲染上下文代理
proxy: null,
exposed: null,
exposeProxy: null,
// 带有with区块的渲染上下文代理
withProxy: null,
// 依赖注入相关
provides: parent ? parent.provides : Object.create(appContext.provides),
// 渲染代理的属性访问缓存
accessCache: null!,
// 渲染缓存
renderCache: [],
// local resolved assets
// 注册的组件
components: null,
// 注册的指令
directives: null,
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
// 派发事件方法
emit: null!, // to be set immediately
emitted: null,
// props default value
propsDefaults: EMPTY_OBJ,
// inheritAttrs
inheritAttrs: type.inheritAttrs,
// state
// 渲染上下文
ctx: EMPTY_OBJ,
// data数据
data: EMPTY_OBJ,
// props数据
props: EMPTY_OBJ,
// 普通属性
attrs: EMPTY_OBJ,
// 插槽相关
slots: EMPTY_OBJ,
// 组件或者DOM的ref引用
refs: EMPTY_OBJ,
// setup 函数返回的响应式结果
setupState: EMPTY_OBJ,
// setup 函数的上下文数据
setupContext: null,
// suspense related
// suspense 相关
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
// suspense 异步依赖
asyncDep: null,
// suspense 异步依赖是否都已处理
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
// 是否挂载
isMounted: false,
// 是否卸载
isUnmounted: false,
// 是否激活
isDeactivated: false,
// 声明周期 befor create
bc: null,
// 声明周期 created
c: null,
// 声明周期 befor mount
bm: null,
// 声明周期 mounted
m: null,
// 声明周期 befor update
bu: null,
// 声明周期 updated
u: null,
// 声明周期 unmounted
um: null,
// 声明周期 befor unmount
bum: null,
// 声明周期 deactivated
da: null,
// 声明周期 activated
a: null,
// render triggered
rtg: null,
// render traked
rtc: null,
// error captured
ec: null,
sp: null
}
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
} else {
// 初始化渲染上下文
instance.ctx = { _: instance }
}
// 初始化根组件指针
instance.root = parent ? parent.root : instance
// 初始化派发事件方法
instance.emit = emit.bind(null, instance)
// apply custom element special handling
if (vnode.ce) {
vnode.ce(instance)
}
return instance
}
接着在同文件看设置组件实例:
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
// 是否是有状态的组件
const isStateful = isStatefulComponent(instance)
// 初始化props
initProps(instance, props, isStateful, isSSR)
// 初始化插槽
initSlots(instance, children)
// 设置有状态的组件实例
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
initProps和initSlots后面再看,现在继续看setupStatefulComponent方法
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
const Component = instance.type as ComponentOptions
// 0. create render proxy property access cache
// 0.创建渲染代理的属性访问缓存
instance.accessCache = Object.create(null)
// 1. create public instance / render proxy
// 1.创建渲染上下文代理
// also mark it raw so it's never observed
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
// 2. call setup()
// 2.判断处理setup函数
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
// 执行setup函数,获取结果
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
// 处理setup执行结果
handleSetupResult(instance, setupResult, isSSR)
} else {
// 完成组件实例设置
finishComponentSetup(instance, isSSR)
}
}
接下来看创建渲染上下文代理,instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)),我们分析proxy的get、set和has方法,首先当我们访问instance.ctx(渲染实例的上下文)中的属性时,就会走get函数:
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
const { ctx, setupState, data, props, accessCache, type, appContext } = instance
// 首先判断key不以$开头,这部分数据可能是setupState、data、props、ctx中的一种,data和props在开发中经常使用,
// setupStata 就是setup函数返回的数据,ctx包括了计算属性、组件方法和用户自定义的数据。
// 如果不以$开头,会依次判断setupState、data、props、ctx中是否包含这个key,如果包含就返回对应值,这个顺序决定在key相
// 同时,取值的优先级
if (key[0] !== '$') {
// 渲染代理的属性访问缓存中
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
}
} else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
accessCache![key] = AccessTypes.SETUP
// 从setupState取
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA
// 从data中取
return data[key]
} else if (
// only cache other properties when instance has declared (thus stable)
// props
(normalizedProps = instance.propsOptions[0]) &&
hasOwn(normalizedProps, key)
) {
accessCache![key] = AccessTypes.PROPS
// 从props中取
return props![key]
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache![key] = AccessTypes.CONTEXT
// 从ctx取
return ctx[key]
} else if (!__FEATURE_OPTIONS_API__ || shouldCacheAccess) {
// 都取不到
accessCache![key] = AccessTypes.OTHER
}
}
const publicGetter = publicPropertiesMap[key]
let cssModule, globalProperties
// public $xxx properties
// 公开的 $xxx 属性或方法
if (publicGetter) {
if (key === '$attrs') {
track(instance, TrackOpTypes.GET, key)
__DEV__ && markAttrsAccessed()
}
return publicGetter(instance)
} else if (
// css module (injected by vue-loader)
// css 模块,通过vue-loader编译时注入
(cssModule = type.__cssModules) &&
(cssModule = cssModule[key])
) {
return cssModule
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
// user may set custom properties to `this` that start with `$`
// 用户自定义属性,也用$开头
accessCache![key] = AccessTypes.CONTEXT
return ctx[key]
} else if (
// global properties
// 全局定义的属性
((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key))
) {
if (__COMPAT__) {
const desc = Object.getOwnPropertyDescriptor(globalProperties, key)!
if (desc.get) {
return desc.get.call(instance.proxy)
} else {
const val = globalProperties[key]
return isFunction(val)
? Object.assign(val.bind(instance.proxy), val)
: val
}
} else {
return globalProperties[key]
}
} else if (
__DEV__ &&
currentRenderingInstance &&
(!isString(key) ||
// #1091 avoid internal isRef/isVNode checks on component instance leading
// to infinite warning loop
key.indexOf('__v') !== 0)
) {
if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
// 如果在data中定义的数据以$开头,会报警告,以为$是保留字符,不会做代理
warn(
`Property ${JSON.stringify(
key
)} must be accessed via $data because it starts with a reserved ` +
`character ("$" or "_") and is not proxied on the render context.`
)
} else if (instance === currentRenderingInstance) {
// 在模板中使用的变量没有定义,报警告
warn(
`Property ${JSON.stringify(key)} was accessed during render ` +
`but is not defined on instance.`
)
}
}
},
}
接下来是set代理的过程,当修改instance.ctx渲染上下文中的属性时,就会进入set函数:
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
set(
{ _: instance }: ComponentRenderContext,
key: string,
value: any
): boolean {
const { data, setupState, ctx } = instance
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
// 给setupState 赋值
setupState[key] = value
return true
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
// 给data 赋值
data[key] = value
return true
} else if (hasOwn(instance.props, key)) {
__DEV__ &&
// 不能给props赋值
warn(
`Attempting to mutate prop "${key}". Props are readonly.`,
instance
)
return false
}
if (key[0] === '$' && key.slice(1) in instance) {
__DEV__ &&
// 不能给Vue内部以$开头的保留属性赋值
warn(
`Attempting to mutate public property "${key}". ` +
`Properties starting with $ are reserved and readonly.`,
instance
)
return false
} else {
if (__DEV__ && key in instance.appContext.config.globalProperties) {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
value
})
} else {
// 用户自定义数据赋值
ctx[key] = value
}
}
return true
},
}
set函数主要是对渲染上下文instance.ctx中的属性赋值,实际上是代理到对应的数据类型中去完成赋值操作,从代码顺序能看到,优先判断的setupState,然后是data,最后是props和用户自定义的数据。
当我们判断属性是否存在于instance.ctx渲染的上下文中,就会进入has函数,例如:
export default {
created () {
console.log('msg' in this)
}
}
has函数的实现:
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
has(
{
_: { data, setupState, accessCache, ctx, appContext, propsOptions }
}: ComponentRenderContext,
key: string
) {
let normalizedProps
// 依次判断key是否在accessCache、data、setupState、props、用户数据、公开属性、全局属性
return (
!!accessCache![key] ||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) ||
hasOwn(appContext.config.globalProperties, key)
)
},
}
然后回到setupStatefulComponent,看一下第二步的判断处理setup函数过程,也就是下面的代码:
const { setup } = Component
if (setup) {
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
// 执行setup函数,获取结果
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
// 处理setup执行结果
handleSetupResult(instance, setupResult, isSSR)
}
首先判断setup函数的参数长度,如果大于1,则创建setupContext上下文:
// 创建setupContext上下文函数中,返回了一个对象,让我们在setup中可以获取到组件的属性、插槽和emit
export function createSetupContext(
instance: ComponentInternalInstance
): SetupContext {
return {
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
},
slots: instance.slots,
emit: instance.emit,
expose
}
}
然后执行setup函数并返回结果callWithErrorHandling
// 其实就是对fn做了一层包装,在有参数的时候传入参数,setup的第一个参数是instance.props,第二个参数是setupContext
export function callWithErrorHandling(
fn: Function,
instance: ComponentInternalInstance | null,
type: ErrorTypes,
args?: unknown[]
) {
let res
try {
res = args ? fn(...args) : fn()
} catch (err) {
handleError(err, instance, type)
}
return res
}
最后处理setup执行结果handleSetupResult:
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
) {
if (isFunction(setupResult)) {
// setup returned an inline render function
// 当模板使用h函数渲染时,setup返回一个函数作为组件的渲染函数
instance.render = setupResult as InternalRenderFunction
} else if (isObject(setupResult)) {
// setup returned bindings.
// assuming a render function compiled from template is present.
// 如果setupResult是对象,把它变成响应式并赋值给instance.setupState,这样模板渲染的时候,
// 根据前面get方法的获取规则,instance.ctx就能从instance.setupState上获取到对应的数据
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance, isSSR)
}
处理完成后,会执行finishComponentSetup去完成组件实例的设置
export function finishComponentSetup(
instance: ComponentInternalInstance,
isSSR: boolean,
skipOptions?: boolean
) {
const Component = instance.type as ComponentOptions
// template / render function normalization
// could be already set when returned from setup()
// 对模板或者渲染函数标准化,主要考虑:
// 1.compile 和组件 template 属性存在,render 方法不存在的情况。
// 2.compile 和 render 方法不存在,组件 template 属性存在的情况。
// 3.组件既没有写 render 函数,也没有写 template 模板,此时要报一个警告
if (!instance.render) {
// only do on-the-fly compile if not in SSR - SSR on-the-fly compilation
// is done by server-renderer
if (!isSSR && compile && !Component.render) {
const template =
(__COMPAT__ &&
instance.vnode.props &&
instance.vnode.props['inline-template']) ||
Component.template
if (template) {
if (__DEV__) {
startMeasure(instance, `compile`)
}
const { isCustomElement, compilerOptions } = instance.appContext.config
const { delimiters, compilerOptions: componentCompilerOptions } =
Component
const finalCompilerOptions: CompilerOptions = extend(
extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
)
if (__COMPAT__) {
// pass runtime compat config into the compiler
finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
if (Component.compatConfig) {
// @ts-expect-error types are not compatible
extend(finalCompilerOptions.compatConfig, Component.compatConfig)
}
}
Component.render = compile(template, finalCompilerOptions)
if (__DEV__) {
endMeasure(instance, `compile`)
}
}
}
// 组件对象的 render 函数赋值给 instance
instance.render = (Component.render || NOOP) as InternalRenderFunction
// for runtime-compiled render functions using `with` blocks, the render
// proxy used needs a different `has` handler which is more performant and
// also only allows a whitelist of globals to fallthrough.
if (installWithProxy) {
installWithProxy(instance)
}
}
// support for 2.x options
// 兼容Vue2.x的Options API
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
applyOptions(instance)
resetTracking()
unsetCurrentInstance()
}
}