该系列文章为个人学习总结,如有错误,欢迎指正;尚有不足,请多指教!
阅读源码暂未涉及ssr服务端渲染,直接跳过
部分调试代码(例如:console.warn等),不涉及主要内容,直接跳过
涉及兼容vue2的代码直接跳过(例如:__FEATURE_OPTIONS_API__等)
注意源码里的
__DEV__
不是指
dev
调试,详情请看
rollup.config.js
mount组件挂载
app.mount('#app')
看似简单的一行代码,但是在
vue
内部做了大量操作。还是回到上文提到的
createApp
函数:
export const createApp = ((...args) => {
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
return proxy
}) as CreateAppFunction<Element>
从上面代码可以看到,app
上mount
主要先校验、获取、清空挂载目标容器,然后再将app
挂载到目标容器上。最终还是调用createAppAPI
函数里的mount。注意:常规的单文本组件被转换成包含setup
、render
等属性的对象。
export function createAppAPI<HostElement>(
render: RootRenderFunction<HostElement>,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
let isMounted = false
const app: App = (context.app = {
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
vnode.appContext = context
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
isMounted = true
app._container = rootContainer
;(rootContainer as any).__vue_app__ = app
return getExposeProxy(vnode.component!) || vnode.component!.proxy
app
的挂载主要分为以下四步:
构建当前app
的虚拟节点,并绑定app
上下文至其appContext
注册热更新后重新加载根节点(同3)
使用渲染器渲染生成的虚拟节点
生成app
的实例代理,暴露对外接口(这里和上一节没有返回app
生成链式调用对应)
生成虚拟节点
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
if (!type || type === NULL_DYNAMIC_COMPONENT) {
type = Comment
if (isVNode(type)) {
const cloned = cloneVNode(type, props, true )
if (children) {
normalizeChildren(cloned, children)
if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
if (cloned.shapeFlag & ShapeFlags.COMPONENT) {
currentBlock[currentBlock.indexOf(type)] = cloned
} else {
currentBlock.push(cloned)
cloned.patchFlag |= PatchFlags.BAIL
return cloned
if (isClassComponent(type)) {
type = type.__vccOpts
if (props) {
props = guardReactiveProps(props)!
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
if (isObject(style)) {
if (isProxy(style) && !isArray(style)) {
style = extend({}, style)
props.style = normalizeStyle(style)
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
这里的_createVNode
只是创建虚拟节点之前做的一些预处理:
如果传入的type
本身就是已经处理过的虚拟节点(例如自带的组件:<component is="com"></component>
),则直接返回其拷贝值。
根据传入的属性props
,预处理一下class
及style
。
使用ShapeFlags
标记当前节点类型
注意,创建vnode
使用的props
是使用组件时传入的参数,和type
(即组件)上定义的组件props
有区别(后续会用到)。
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props)
} as VNode
return vnode
调用createBaseVNode
函数初始化虚拟节点对象。
调用render
渲染组件
const patch: PatchFn = (
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
if (n1 === n2) {
return
const { type, ref, shapeFlag } = n2
switch (type) {
default:
else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
} else {
* 传入的参数依次是:
* 旧的虚拟节点
* 新的虚拟节点
* 目标dom
* 父组件
* 异步组件父组件
* 是否是svg
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
flushPreFlushCbs
()
flushPostFlushCbs()
container._vnode = vnode
可以看出渲染虚拟节点主要是执行patch
方法。由于文章篇幅有限,就以常规组件——虚拟节点类型为ShapeFlags.COMPONENT
的执行过程为例,其他类型就不一一列举。
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(
container,
anchor,
isSVG,
optimized
} else {
mountComponent(
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
} else {
updateComponent(n1, n2, optimized)
这里处理组件的方法processComponent
也很简单,旧节点不存在就调用挂载组件(这里存在处理keep-alive
的后续讨论),否则就调用更新组件。
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
if (!(__COMPAT__ && compatMountInstance)) {
setupComponent(instance)
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
这里组件挂载函数经过简化后,依次执行了如下代码:
创建组件实例:createComponentInstance
装载组件: setupComponent
关联响应式更新: setupRenderEffect
let uid = 0
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
const type = vnode.type as ConcreteComponent
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type,
parent,
appContext,
provides: parent ? parent.provides : Object.create(appContext.provides),
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
} else {
instance.ctx = { _: instance }
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
export function createDevRenderContext(instance: ComponentInternalInstance) {
const target: Record<string, any> = {}
Object.defineProperty(target, `_`, {
configurable: true,
enumerable: false,
get: () => instance
Object.keys(publicPropertiesMap).forEach(key => {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
get: () => publicPropertiesMap[key](instance),
set: NOOP
return target as ComponentRenderContext
export const publicPropertiesMap: PublicPropertiesMap =
extend(Object.create(null), {
$: i => i,
$el: i => i.vnode.el,
$data: i => i.data,
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
$parent: i => getPublicInstance(i.parent),
$root: i => getPublicInstance(i.root),
$emit: i => i.emit,
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
$forceUpdate: i => i.f || (i.f = () => queueJob(i.update)),
$nextTick: i => i.n || (i.n = nextTick.bind(i.proxy!)),
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
} as PublicPropertiesMap)
export function emit(
instance: ComponentInternalInstance,
event: string,
...rawArgs: any[]
if (instance.isUnmounted) return
const props = instance.vnode.props || EMPTY_OBJ
if (__DEV__) {
const {
emitsOptions,
propsOptions: [propsOptions]
} = instance
if (emitsOptions) {
const validator = emitsOptions[event]
if (isFunction(validator)) {
const isValid = validator(...rawArgs)
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`
let args = rawArgs
const isModelListener = event.startsWith('update:')
const modelArg = isModelListener && event.slice(7)
if (modelArg && modelArg in props) {
const modifiersKey = `${
modelArg === 'modelValue' ? 'model' : modelArg
}Modifiers`
const { number, trim } = props[modifiersKey] || EMPTY_OBJ
if (trim) {
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
if (number) {
args = rawArgs.map(looseToNumber)
let handlerName
let handler =
props[(handlerName = toHandlerKey(event))] ||
props[(handlerName = toHandlerKey(camelize(event)))]
if (!handler && isModelListener) {
handler = props[(handlerName = toHandlerKey(hyphenate(event)))]
if (handler) {
callWithAsyncErrorHandling(
handler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
const onceHandler = props[handlerName + `Once`]
if (onceHandler) {
if (!instance.emitted) {
instance.emitted
= {} as Record<any, boolean>
} else if (instance.emitted[handlerName]) {
return
instance.emitted[handlerName] = true
callWithAsyncErrorHandling(
onceHandler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
以上代码初始化了一些组件实例属性,预处理的组件定义的参数格式等。另外provides
属性是从App
上定义的原型上的一个对象,往子孙组件传递。ctx
是实例上下文,在单文本组件中template
内可以直接使用的属性,注意这里的$props
、$attrs
、$slots
、$refs
都是响应式变量。
export 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
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number,
isSSR = false
const props: Data = {}
const attrs: Data = {}
def(attrs, InternalObjectKey, 1)
instance.propsDefaults = Object.create(null)
setFullProps(instance, rawProps, props, attrs)
for (const key in instance.propsOptions[0]) {
if (!(key in props)) {
props[key] = undefined
if (isStateful) {
instance.props = isSSR ? props : shallowReactive(props)
} else {
if (!instance.type.props) {
instance.props = attrs
} else {
instance.props = props
instance.attrs = attrs
initProps
方法主要是格式化组件定义属性和组件传入属性,使传入属性对应上定义属性。组件实例的props
被shallowReactive
而不是reactive
本意为了维持数据流从父组件流向子组件这一开发思路。因为只有父组件可以修改props
的第一层属性触发子组件响应式更新。至于props
的深层属性的值变化,可以来自任何地方。
注意:未在组件内注册,但传入的事件,会被设置在attrs
上。
function createAttrsProxy(instance: ComponentInternalInstance): Data {
return new Proxy(
instance.attrs, {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
export function createSetupContext(
instance: ComponentInternalInstance
): SetupContext {
const expose: SetupContext['expose'] = exposed => {
instance.exposed = exposed || {}
let attrs: Data
if (__DEV__) {
return Object.freeze({
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
get slots() {
return shallowReadonly(instance.slots)
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
expose
} else {
return {
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
slots: instance.slots,
emit: instance.emit,
expose
export function useSlots(): SetupContext['slots'] {
return getContext().slots
export function useAttrs(): SetupContext['attrs'] {
return getContext().attrs
function getContext(): SetupContext {
const i = getCurrentInstance()!
return i.setupContext || (i.setupContext = createSetupContext(i))
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
const Component = instance.type as ComponentOptions
instance.accessCache = Object.create(null)
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
if (__DEV__) {
exposePropsOnRenderContext(instance)
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)) {
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
} else {
handleSetupResult(instance, setupResult, isSSR)
} else {
finishComponentSetup(instance, isSSR)
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
const { ctx, setupState, data, props, accessCache, type, appContext } =
instance
let normalizedProps
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]
} else if (hasSetupBinding(setupState, key)) {
accessCache![key] = AccessTypes.SETUP
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA
return data[key]
} else if (
(normalizedProps = instance.propsOptions[0]) &&
hasOwn(normalizedProps, key)
accessCache![key] = AccessTypes.PROPS
return props![key]
} else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
accessCache![key] = AccessTypes.CONTEXT
return ctx[key]
} else if (!__FEATURE_OPTIONS_API__ || shouldCacheAccess) {
accessCache![key] = AccessTypes.OTHER
const publicGetter = publicPropertiesMap[key]
let cssModule, globalProperties
if (publicGetter) {
if (key === '$attrs') {
track(instance, TrackOpTypes.GET, key)
__DEV__ && markAttrsAccessed()
return publicGetter(instance)
在执行setup
前后分别进行了setCurrentInstance
和unsetCurrentInstance
操作,目的就是创建组件时提供一个全局的实例变量,这样在setup
函数外部也能获取当前instance
。这也是为什么一些与instance
相关的函数:生命周期钩子函数(例如:onMounted
、onBeforeMount
、onUnmounted
等)、组合函数(例如:provide
、inject
、useAttrs
、 useSlots
等)必须在setup
函数内执行的原因。
需要注意的是:provide(key, value)
方法的key
值除了字符、数字类型外还可以使用symbol
类型,value
值没有做响应式处理,这样做是为了避免响应式更新导致父子孙组件全部更新,从而容易造成性能问题。
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
isSSR: boolean
if (isFunction(setupResult)) {
instance.render = setupResult as InternalRenderFunction
} else if (isObject(setupResult)) {
instance.setupState = proxyRefs(setupResult)
if (__DEV__) {
exposeSetupStateOnRenderContext(instance)
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
setupResult === null ? 'null' : typeof setupResult
finishComponentSetup(instance, isSSR)
handleSetupResult
函数主要是代理ctx
和setup
执行结果,setup
返回函数用作render
。finishComponentSetup
函数主要是设置当前实例的渲染函数,常规组件由SFC生成。
至此,组件装载完成,它主要执行以下任务:
格式化当前组件传参,并shallowReactive
代理
格式化当前组件传入插槽
生成当前实例代理:PublicInstanceProxyHandlers -> instance.proxy
生成属性代理:props -> instance.ctx
执行setup
函数,执行结果生成ref
代理,并代理至:setupResult -> instance.ctx
实例上设置render
方法
接下来才是组件挂载最重要的一步:setupRenderEffect
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
toggleRecurse(instance, false)
if (bm) {
invokeArrayFns(bm)
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeBeforeMount)
invokeVNodeHook(vnodeHook, parent, initialVNode)
toggleRecurse(instance, true)
if (el && hydrateNode) {
} else {
if (__DEV__) {
startMeasure(instance, `render`)
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
if (__DEV__) {
startMeasure(instance, `patch`)
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
if (__DEV__) {
endMeasure(instance, `patch`)
initialVNode.el = subTree.el
if (m) {
queuePostRenderEffect(m, parentSuspense)
!isAsyncWrapperVNode &&
(vnodeHook = props && props.onVnodeMounted)
const scopedInitialVNode = initialVNode
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
parentSuspense
instance.isMounted = true
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentAdded(instance)
initialVNode = container = anchor = null as any
} else {
let { next, bu, u, parent, vnode } = instance
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
toggleRecurse(instance, false)
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
if (bu) {
invokeArrayFns(bu)
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
toggleRecurse(instance, true)
if (__DEV__) {
startMeasure(instance, `render`)
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
const prevTree = instance.subTree
instance.subTree = nextTree
if (__DEV__) {
startMeasure(instance, `patch`)
patch(
prevTree,
nextTree,
hostParentNode(prevTree.el!)!,
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
if (__DEV__) {
endMeasure(instance, `patch`)
next.el = nextTree.el
if (originNext === null) {
updateHOCHostEl(instance, nextTree.el)
if (u) {
queuePostRenderEffect(u, parentSuspense)
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(
() => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
parentSuspense
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
devtoolsComponentUpdated(instance)
if (__DEV__) {
popWarningContext()
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
instance.scope
const update: SchedulerJob = (instance.update = () => effect.run())
update.id = instance.uid
toggleRecurse(instance, true)
if (__DEV__) {
effect.onTrack = instance.rtc
? e => invokeArrayFns(instance.rtc!, e)
: void 0
effect.onTrigger = instance.rtg
? e => invokeArrayFns(instance.rtg!, e)
: void 0
update.ownerInstance = instance
update()
setupRenderEffect
主要通过ReactiveEffect
类构造包裹componentUpdateFn
函数的响应式effect
(这个effect后续讨论),它的主要作用就是在componentUpdateFn
函数执行时收集响应式数据,后续依赖更新时重新执行之。
在挂载和更新组件时,都会执行renderComponentRoot
方法,其主要内容就是执行组件生成的render
方法生成虚拟节点。这里也可以看出组件更新只会执行template
内容,并不会重新执行setup
方法。
componentUpdateFn
函数主要是执行render
函数,其结果作为子组件,继续执行挂载/更新操作,这里篇幅有限,就不一一列举各种类型的组件挂载。挂载流程参考如下简图:
componentUpdateFn
执行过程中触发生命周期钩子函数,根据如上流程图,可以看出组件挂载生命周期执行顺序:
setup(父) -> beforeMount(父) -> setup(子) -> beforeMount(子) -> mounted(子) -> mounted(父)
beforeUpdate
(父) -> beforeUpdate(子) -> updated(子) -> updated(父)
最后补充一个细节,patch
函数在最后执行了setRef
方法,这里回到上面创建虚拟节点函数内
const normalizeRef = ({
ref_key,
ref_for
}: VNodeProps): VNodeNormalizedRefAtom | null => {
return (
ref != null
? isString(ref) || isRef(ref) || isFunction(ref)
? { i: currentRenderingInstance, r: ref, k: ref_key, f: !!ref_for }
: ref
: null
) as any
function createBaseVNode(
const vnode = {
ref: props && normalizeRef(props)
} as VNode
return vnode
const patch: PatchFn = (
) => {
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
export function setRef(
rawRef: VNodeNormalizedRef,
oldRawRef: VNodeNormalizedRef | null,
parentSuspense: SuspenseBoundary | null,
vnode: VNode,
isUnmount = false
if (isArray(rawRef)) {
rawRef.forEach((r, i) =>
setRef(
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
parentSuspense,
vnode,
isUnmount
return
if (isAsyncWrapper(vnode) && !isUnmount) {
return
const refValue =
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
? getExposeProxy(vnode.component!) || vnode.component!.proxy
: vnode.el
const value = isUnmount ? null : refValue
const { i: owner, r: ref } = rawRef
if (__DEV__ && !owner) {
warn(
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
`A vnode with ref must be created inside the render function.`
return
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
const setupState = owner.setupState
if (oldRef != null && oldRef !== ref) {
if (isString(oldRef)) {
refs[oldRef] = null
if (hasOwn(setupState, oldRef)) {
setupState[oldRef] = null
} else if (isRef(oldRef)) {
oldRef.value = null
if (isFunction(ref)) {
callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
} else {
const _isString = isString(ref)
const _isRef = isRef(ref)
if (_isString || _isRef) {
const doSet = () => {
if (rawRef.f) {
const existing = _isString
? hasOwn(setupState, ref)
? setupState[ref]
: refs[ref]
: ref.value
if (isUnmount) {
isArray(existing) && remove(existing, refValue)
} else {
if (!isArray(existing)) {
if (_isString) {
refs[ref] = [refValue]
if (hasOwn(setupState, ref)) {
setupState[ref] = refs[ref]
} else {
ref.value = [refValue]
if (rawRef.k) refs[rawRef.k] = ref.value
} else if (!existing.includes(refValue)) {
existing.push(refValue)
} else if (_isString) {
refs[ref] = value
if (hasOwn(setupState, ref)) {
setupState[ref] = value
} else if (_isRef) {
ref.value = value
if (rawRef.k) refs[rawRef.k] = value
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
if (value) {
;(doSet as SchedulerJob).id = -1
queuePostRenderEffect(doSet, parentSuspense)
} else {
doSet()
} else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`)
组件或dom
上设置ref
,挂载后会将当前实例或dom写入至ref对应的变量,写入规则:
可传入的值:string、ref、function(注意:reactive对象、数值、symbol等类型无效)
ref传入string,写入为setupState
(及setup
返回对象)上对应的值,不存在就写入实例refs
上。ref
配置在v-for
循环上时,写入的值为v-for
循环形成的数组。
ref传入ref时,直接写入ref.value
,v-for
循环上时,写入的值为v-for
循环形成的数组
ref传入函数时,依次传入两个参数:value, refs
;v-for
时会循环执行传入函数。注意:如果需要暴露在$refs
上,需要手动设置refs
。
设置ref
后,值会被同步至refs
上,对外暴露在$refs
。
这里总结一下app
及组件挂载过程中的一些细节:
app
挂载返回的是getExposeProxy
方法返回的代理,组件挂载后可以通过设置ref
获取实例(本质也是getExposeProxy
方法返回的代理)。在setup或生命周期内调用vue函数getCurrentInstance
获取未代理的内部实例(不推荐)。
template
内可访问的变量:$props
、$attrs
、$slots
、$refs
都是响应式变量;setup
函数返回对象、定义的props
的key
值可作为变量直接访问。$refs
是设置了ref
的组件实例或dom;其他属性(例如:、el、options、parent等)不建议直接使用。
生命周期钩子函数(例如:onMounted、onBeforeMount、onUnmounted等)、组合函数(例如:provide、inject、useAttrs、 useSlots等)必须在setup函数内执行
组件更新只会执行template
内容,并不会重新执行setup
方法
- 92
-
全栈弄潮儿
JavaScript
Vue.js