vue3源码学习13-props初始化及更新
1.props初始化
之前发现在setup启动函数中会初始化Props:
// packages/runtime-core/src/component.ts
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
}
可以看到初始化props就是通过initProps方法:
// packages/runtime-core/src/componentProps.ts
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number, // result of bitwise flag comparison
isSSR = false
) {
const props: Data = {}
const attrs: Data = {}
def(attrs, InternalObjectKey, 1)
instance.propsDefaults = Object.create(null)
// 1.设置 props 的值
setFullProps(instance, rawProps, props, attrs)
// ensure all declared prop keys are present
for (const key in instance.propsOptions[0]) {
if (!(key in props)) {
props[key] = undefined
}
}
// validation
// 2.验证 props 的值
if (__DEV__) {
validateProps(rawProps || {}, props, instance)
}
if (isStateful) {
// stateful
// 3.有状态的组件做响应式处理
instance.props = isSSR ? props : shallowReactive(props)
} else {
// 函数式组件处理
if (!instance.type.props) {
// functional w/ optional props, props === attrs
instance.props = attrs
} else {
// functional w/ declared props
instance.props = props
}
}
// 普通属性赋值
instance.attrs = attrs
}
设置props
// 只分析有状态组件的props,也就是isStateful是true
function setFullProps(
// 组件实例
instance: ComponentInternalInstance,
// 原始的props值
rawProps: Data | null,
// 解析后的 props 值
props: Data,
// 解析后的普通属性
attrs: Data
) {
// 标准化 props 的配置
const [options, needCastKeys] = instance.propsOptions
let hasAttrsChanged = false
let rawCastValues: Data | undefined
if (rawProps) {
for (let key in rawProps) {
// key, ref are reserved and never passed down
// 一些保留的prop ,如 ref、key不会传递
if (isReservedProp(key)) {
continue
}
const value = rawProps[key]
// prop option names are camelized during normalization, so to support
// kebab -> camel conversion here we need to camelize the key.
// 连字符形式的 props 也转成驼峰形式, 对比看prop是否在配置中定义
let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) {
if (!needCastKeys || !needCastKeys.includes(camelKey)) {
// 如果在配置中定义了,把值复制到props对象中
props[camelKey] = value
} else {
;(rawCastValues || (rawCastValues = {}))[camelKey] = value
}
} else if (!isEmitListener(instance.emitsOptions, key)) {
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing
// 非事件派发相关的,且不在 props 中定义的普通属性用attrs保留
if (__COMPAT__) {
if (isOn(key) && key.endsWith('Native')) {
key = key.slice(0, -6) // remove Native postfix
} else if (shouldSkipAttr(key, instance)) {
continue
}
}
if (!(key in attrs) || value !== attrs[key]) {
attrs[key] = value
hasAttrsChanged = true
}
}
}
}
if (needCastKeys) {
// 需要做转换的 props
const rawCurrentProps = toRaw(props)
const castValues = rawCastValues || EMPTY_OBJ
for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i]
props[key] = resolvePropValue(
options!,
rawCurrentProps,
key,
castValues[key],
instance,
!hasOwn(castValues, key)
)
}
}
return hasAttrsChanged
}
需要装换的props,遍历执行resolvePropValue方法求值:
function resolvePropValue(
options: NormalizedProps,
props: Data,
key: string,
value: unknown,
instance: ComponentInternalInstance,
isAbsent: boolean
) {
const opt = options[key]
if (opt != null) {
const hasDefault = hasOwn(opt, 'default')
// default values
// 默认值处理,且父组件没有传递数据,prop取默认值
if (hasDefault && value === undefined) {
const defaultValue = opt.default
if (opt.type !== Function && isFunction(defaultValue)) {
const { propsDefaults } = instance
if (key in propsDefaults) {
value = propsDefaults[key]
} else {
setCurrentInstance(instance)
value = propsDefaults[key] = defaultValue.call(
__COMPAT__ &&
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
? createPropsDefaultThis(instance, props, key)
: null,
props
)
unsetCurrentInstance()
}
} else {
value = defaultValue
}
}
// boolean casting
// 布尔类型的值
if (opt[BooleanFlags.shouldCast]) {
if (isAbsent && !hasDefault) {
// 如下,prop指定仅能是Boolean类型,父子间没有传值就是false
// export default {
// props: {
// author: Boolean
// }
// }
value = false
} else if (
opt[BooleanFlags.shouldCastTrue] &&
(value === '' || value === hyphenate(key))
) {
// 如下,prop指定是Boolean或String类型,且Boolean类型在前,如果喜欢滴的是空字符串,prop值是true
// export default {
// props: {
// author: [Boolean, String]
// }
// }
value = true
}
}
}
return value
}
props转换求值结束
验证props
function validateProps(
rawProps: Data,
props: Data,
instance: ComponentInternalInstance
) {
const resolvedValues = toRaw(props)
const options = instance.propsOptions[0]
for (const key in options) {
let opt = options[key]
if (opt == null) continue
validateProp(
key,
resolvedValues[key],
opt,
!hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
)
}
}
function validateProp(
name: string,
value: unknown,
prop: PropOptions,
isAbsent: boolean
) {
const { type, required, validator } = prop
// required!
// 检测required
if (required && isAbsent) {
warn('Missing required prop: "' + name + '"')
return
}
// missing but optional
// 没有传值,也没有required,直接返回
if (value == null && !prop.required) {
return
}
// type check
// 类型检查
if (type != null && type !== true) {
let isValid = false
const types = isArray(type) ? type : [type]
const expectedTypes = []
// value is valid as long as one of the specified types match
// 只要指定的类型之一匹配,就是有效值
for (let i = 0; i < types.length && !isValid; i++) {
const { valid, expectedType } = assertType(value, types[i])
expectedTypes.push(expectedType || '')
isValid = valid
}
if (!isValid) {
warn(getInvalidTypeMessage(name, value, expectedTypes))
return
}
}
// custom validator
// 自定义的校验器
if (validator && !validator(value)) {
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
}
}
校验结束,接着是
响应式处理
使用shallowReactive而不是普通的reactive,因为shallowReactive创建getter函数时,shallow变量为true,这样不会递归执行reactive,只劫持最外一层对象,把对象target的最外一层属性的访问和修改处理成响应式。
shallowReactive实现:
// packages/reactivity/src/reactive.ts
export function shallowReactive<T extends object>(
target: T
): ShallowReactive<T> {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
getter的处理器函数:
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
2.prop更新
props数据更新会触发组件的重新渲染,在组件更新的章节知道,组件的重新渲染会触发patch过程,然后遍历子节点递归patch,遇到组件节点,会执行updateComponent方法:
packages/runtime-core/src/renderer.ts
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
const instance = (n2.component = n1.component)!
// 根据新旧子组件 vnode 判断是否需要更新子组件
if (shouldUpdateComponent(n1, n2, optimized)) {
// 正常更新
// normal update
// 新的子组件 vnode 赋值给 instance.next
instance.next = n2
// in case the child component is also queued, remove it to avoid
// double updating the same child component in the same flush.
// 子组件可能因为数据变化被添加到更新队列里了,移除他们防止对一个子组件重复更新
invalidateJob(instance.update)
// instance.update is the reactive effect.
// 执行子组件的副作用函数
instance.update()
} else {
// no update needed. just copy over properties
// 不需要更新,只复制属性
n2.el = n1.el
instance.vnode = n2
}
}
执行instance.update,实际就是执行componentEffect 组件副作用渲染函数:
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
// 渲染组件
} else {
// 更新组件
let { next, bu, u, parent, vnode } = instance
// next 表示新的组件vnode
if (next) {
next.el = vnode.el
// 更新组件vnode节点信息
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// 渲染新的子树 vnode
const nextTree = renderComponentRoot(instance)
// 缓存旧的子树 vnode
const prevTree = instance.subTree
// 更新子树 vnode
instance.subTree = nextTree
// 组件更新核心,patch
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
// 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
// 参考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
// 缓存更新后的DOM节点
next.el = nextTree.el
}
}
}
更新组件时,会执行updateComponentPreRender更新组件vnode节点信息:
const updateComponentPreRender = (
instance: ComponentInternalInstance,
nextVNode: VNode,
optimized: boolean
) => {
nextVNode.component = instance
const prevProps = instance.vnode.props
instance.vnode = nextVNode
instance.next = null
updateProps(instance, nextVNode.props, prevProps, optimized)
updateSlots(instance, nextVNode.children, optimized)
pauseTracking()
// props update may have triggered pre-flush watchers.
// flush them before the render update.
flushPreFlushCbs()
resetTracking()
}
其中会执行updateProps更新props:
// packages/runtime-core/src/componentProps.ts
export function updateProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
rawPrevProps: Data | null,
optimized: boolean
) {
const {
props,
attrs,
vnode: { patchFlag }
} = instance
const rawCurrentProps = toRaw(props)
const [options] = instance.propsOptions
let hasAttrsChanged = false
if (
// always force full diff in dev
// - #1942 if hmr is enabled with sfc component
// - vite#872 non-sfc component used by sfc component
!(
__DEV__ &&
(instance.type.__hmrId ||
(instance.parent && instance.parent.type.__hmrId))
) &&
(optimized || patchFlag > 0) &&
!(patchFlag & PatchFlags.FULL_PROPS)
) {
if (patchFlag & PatchFlags.PROPS) {
// Compiler-generated props & no keys change, just set the updated
// the props.
// 只更新动态 props 节点
const propsToUpdate = instance.vnode.dynamicProps!
for (let i = 0; i < propsToUpdate.length; i++) {
let key = propsToUpdate[i]
// skip if the prop key is a declared emit event listener
if (isEmitListener(instance.emitsOptions, key)) {
continue
}
// PROPS flag guarantees rawProps to be non-null
const value = rawProps![key]
if (options) {
// attr / props separation was done on init and will be consistent
// in this code path, so just check if attrs have it.
if (hasOwn(attrs, key)) {
if (value !== attrs[key]) {
attrs[key] = value
hasAttrsChanged = true
}
} else {
const camelizedKey = camelize(key)
props[camelizedKey] = resolvePropValue(
options,
rawCurrentProps,
camelizedKey,
value,
instance,
false /* isAbsent */
)
}
} else {
if (__COMPAT__) {
if (isOn(key) && key.endsWith('Native')) {
key = key.slice(0, -6) // remove Native postfix
} else if (shouldSkipAttr(key, instance)) {
continue
}
}
if (value !== attrs[key]) {
attrs[key] = value
hasAttrsChanged = true
}
}
}
}
} else {
// full props update.
// 全量props更新
if (setFullProps(instance, rawProps, props, attrs)) {
hasAttrsChanged = true
}
// in case of dynamic props, check if we need to delete keys from
// the props object
// 因为新的props是动态的,把那些不在新的props中但存在于旧的props中的值设置为undefined
let kebabKey: string
for (const key in rawCurrentProps) {
if (
!rawProps ||
// for camelCase
(!hasOwn(rawProps, key) &&
// it's possible the original props was passed in as kebab-case
// and converted to camelCase (#955)
((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))
) {
if (options) {
if (
rawPrevProps &&
// for camelCase
(rawPrevProps[key] !== undefined ||
// for kebab-case
rawPrevProps[kebabKey!] !== undefined)
) {
props[key] = resolvePropValue(
options,
rawCurrentProps,
key,
undefined,
instance,
true /* isAbsent */
)
}
} else {
delete props[key]
}
}
}
// in the case of functional component w/o props declaration, props and
// attrs point to the same object so it should already have been updated.
if (attrs !== rawCurrentProps) {
for (const key in attrs) {
if (
!rawProps ||
(!hasOwn(rawProps, key) &&
(!__COMPAT__ || !hasOwn(rawProps, key + 'Native')))
) {
delete attrs[key]
hasAttrsChanged = true
}
}
}
}
// trigger updates for $attrs in case it's used in component slots
if (hasAttrsChanged) {
trigger(instance, TriggerOpTypes.SET, '$attrs')
}
if (__DEV__) {
validateProps(rawProps || {}, props, instance)
}
}