<script src="../dist/vue.global.js"></script>
<div id="app">
<h1 class="red" :title="title">{{title}}</h1>
</div>
<script>
const {
createApp,
ref
} = Vue
createApp({
setup() {
const title = ref('vue3')
return {
title
}
}
})
.mount('#app')
</script>
思考:在什么时候属性初始化?
我猜:在挂载element中初始化,(mountElement)
mountElement
if (props) {
for (const key in props) {
if (key !== 'value' && !isReservedProp(key)) {
hostPatchProp(
el,
key,
null,
props[key],
isSVG,
vnode.children as VNode[],
parentComponent,
parentSuspense,
unmountChildren
)
}
}
在这个例子中
const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode
props:{
class: 'red',
title: 'vue3'
}
hostPatchProp从何而来
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
setScopeId: hostSetScopeId = NOOP,
cloneNode: hostCloneNode,
insertStaticContent: hostInsertStaticContent
} = options
options是baseCreateRenderer的形参
回忆一下
createApp -> ensureRenderer -> createRenderer(rendererOptions) -> baseCreateRenderer(options)
这里的options 就是上面的options
这里是浏览器特有的属性 runtime-dom,不同的平台会又不同的操作,所以从外部定义传入
const rendererOptions = extend({ patchProp }, nodeOps)
export const patchProp: DOMRendererOptions['patchProp'] = (
el,
key,
prevValue,
nextValue,
isSVG = false,
prevChildren,
parentComponent,
parentSuspense,
unmountChildren
) => {
if (key === 'class') {
patchClass(el, nextValue, isSVG)
} else if (key === 'style') {
patchStyle(el, prevValue, nextValue)
} else if (isOn(key)) {
// 事件处理
// ignore v-model listeners
if (!isModelListener(key)) {
patchEvent(el, key, prevValue, nextValue, parentComponent)
}
} else if (
key[0] === '.'
? ((key = key.slice(1)), true)
: key[0] === '^'
? ((key = key.slice(1)), false)
: shouldSetAsProp(el, key, nextValue, isSVG)
) {
patchDOMProp(
el,
key,
nextValue,
prevChildren,
parentComponent,
parentSuspense,
unmountChildren
)
} else {
// special case for <input v-model type="checkbox"> with
// :true-value & :false-value
// store value as dom properties since non-string values will be
// stringified.
if (key === 'true-value') {
;(el as any)._trueValue = nextValue
} else if (key === 'false-value') {
;(el as any)._falseValue = nextValue
}
patchAttr(el, key, nextValue, isSVG, parentComponent)
}
}
class="red"则进入第一个判断patchClass
export function patchClass(el: Element, value: string | null, isSVG: boolean) {
// directly setting className should be faster than setAttribute in theory
// if this is an element during a transition, take the temporary transition
// classes into account.
const transitionClasses = (el as ElementWithTransition)._vtc
if (transitionClasses) {
value = (
value ? [value, ...transitionClasses] : [...transitionClasses]
).join(' ')
}
if (value == null) {
el.removeAttribute('class')
} else if (isSVG) {
el.setAttribute('class', value)
} else {
el.className = value
}
}
:title="title"则进入patchDOMProp
el[key] = value
例子
<script src="../dist/vue.global.js"></script>
<div id="app">
<h1 :title="title">{{title}}</h1>
<comp foo="fooval" bar="barval"></comp>
</div>
<script>
Vue.createApp({
data() {
return {
title: 'randy'
}
},
})
.component('comp', {
props: ['foo'],
template: `
<div>comp</div>
<p>{{foo}}</p>
<p>{{$attrs.bar}}</p>
`
})
.mount('#app')
</script>
在setupComponent中从vnode中拿到props
props: {
bar: "bar"
foo: "foo"
}
执行initProps
export function initProps(
instance: ComponentInternalInstance, // 组件实例对象
rawProps: Data | null, //vnode中获取的props
isStateful: number, // result of bitwise flag comparison
isSSR = false
) {
const props: Data = {}
const attrs: Data = {}
def(attrs, InternalObjectKey, 1)
instance.propsDefaults = Object.create(null)
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
}
}
if (isStateful) {
// stateful
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 和 attrs 两个对象,猜测下面的处理就是把vnode中获取的props进行拆分
执行了setFullProps
function setFullProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
props: Data,
attrs: Data
) {
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
if (isReservedProp(key)) {
continue
}
if (__COMPAT__) {
if (key.startsWith('onHook:')) {
softAssertCompatEnabled(
DeprecationTypes.INSTANCE_EVENT_HOOKS,
instance,
key.slice(2).toLowerCase()
)
}
if (key === 'inline-template') {
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.
let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) {
if (!needCastKeys || !needCastKeys.includes(camelKey)) {
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
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) {
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
}
这里出现了instance.propsOptions 还对此进行了结构 拿到了options, needCastKeys
疑问:instance.propsOptions 这个是在哪里定义的呢 ,首先我想到的是实例创建的地方也就是createComponentInstance这个方法
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
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
···
propsOptions: normalizePropsOptions(type, appContext),
···
}
···
return instance
}
export function normalizePropsOptions(
comp: ConcreteComponent,
appContext: AppContext,
asMixin = false
): NormalizedPropsOptions {
const cache = appContext.propsCache
const cached = cache.get(comp)
if (cached) {
return cached
}
const raw = comp.props
const normalized: NormalizedPropsOptions[0] = {}
const needCastKeys: NormalizedPropsOptions[1] = []
// apply mixin/extends props
let hasExtends = false
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
const extendProps = (raw: ComponentOptions) => {
if (__COMPAT__ && isFunction(raw)) {
raw = raw.options
}
hasExtends = true
const [props, keys] = normalizePropsOptions(raw, appContext, true)
extend(normalized, props)
if (keys) needCastKeys.push(...keys)
}
if (!asMixin && appContext.mixins.length) {
appContext.mixins.forEach(extendProps)
}
if (comp.extends) {
extendProps(comp.extends)
}
if (comp.mixins) {
comp.mixins.forEach(extendProps)
}
}
if (!raw && !hasExtends) {
cache.set(comp, EMPTY_ARR as any)
return EMPTY_ARR as any
}
if (isArray(raw)) {
for (let i = 0; i < raw.length; i++) {
if (__DEV__ && !isString(raw[i])) {
warn(`props must be strings when using array syntax.`, raw[i])
}
const normalizedKey = camelize(raw[i])
if (validatePropName(normalizedKey)) {
normalized[normalizedKey] = EMPTY_OBJ
}
}
} else if (raw) {
if (__DEV__ && !isObject(raw)) {
warn(`invalid props options`, raw)
}
for (const key in raw) {
const normalizedKey = camelize(key)
if (validatePropName(normalizedKey)) {
const opt = raw[key]
const prop: NormalizedProp = (normalized[normalizedKey] =
isArray(opt) || isFunction(opt) ? { type: opt } : opt)
if (prop) {
const booleanIndex = getTypeIndex(Boolean, prop.type)
const stringIndex = getTypeIndex(String, prop.type)
prop[BooleanFlags.shouldCast] = booleanIndex > -1
prop[BooleanFlags.shouldCastTrue] =
stringIndex < 0 || booleanIndex < stringIndex
// if the prop needs boolean casting or default value
if (booleanIndex > -1 || hasOwn(prop, 'default')) {
needCastKeys.push(normalizedKey)
}
}
}
}
}
const res: NormalizedPropsOptions = [normalized, needCastKeys]
cache.set(comp, res)
return res
}
拿到 raw ['foo']
定义normalized 为 {}
定义needCastKeys 为[] 进行了一些列的标准化
将foo设置为normalized的属性
此时
normalized: {
foo: {}
}
所以回到上面
instance.propsOptions = [
{
foo: {}
},
[]
]
所以我现在理解了setFullProps这个函数名字的意思,instance.propsOptions是一个空的 需要通过这个函数填满
所以setFullProps就是给之前设置的props 和 attrs 赋值的
所以此时
props: {
foo: 'fooval'
}
attrs: {
bar: 'barval'
}
然后再吧props 和 attrs放到Instance实例上面