组件 props 和 attrs 的初始化
2025-10-29 11:35:33
上一节已经可以挂载组件,并在组件内部完成响应式更新。
但组件还需要接收外部传入的参数:
import { createApp, h } from '../dist/vue.esm.js'
const App = {
props: {
msg: String
},
setup(props, { attrs }) {
console.log('props', props)
console.log('attrs', attrs)
},
render() {
return h('div', 'hello world')
}
}
createApp(App, { msg: 'hello', count: 1 }).mount('#app')
传入了两个参数:
{
msg: 'hello',
count: 1
}
组件只声明了 msg,所以最终会被拆分成:
props: {
msg: 'hello'
},
attrs: {
count: 1
}
props:组件声明并接收的参数attrs:没有被组件声明的参数
接下来就实现组件 props 和 attrs 的初始化。
处理 props 声明
声明 props 有两种写法:
const App = {
props: ['msg']
}
或者:
const App = {
props: {
msg: String
}
}
先把数组形式转换成对象形式。
组件 props 相关的逻辑放在 componentProps.ts 中:
// packages/runtime-core/src/componentProps.ts
export function normalizePropsOptions(props = {}) {
if (isArray(props)) {
return props.reduce((normalized, key) => {
normalized[key] = {}
return normalized
}, {})
}
return props
}
数组形式的声明:
['msg', 'count']
会被转换成:
{
msg: {},
count: {}
}
保存 props 配置
在创建组件实例时,保存组件声明的 props 配置:
// packages/runtime-core/src/component.ts
import { normalizePropsOptions } from './componentProps'
export function createComponentInstance(vnode) {
const { type } = vnode
const instance = {
type,
vnode,
render: null,
propsOptions: normalizePropsOptions(type.props),
setupState: null,
props: {},
attrs: {},
subTree: null,
isMounted: false
}
return instance
}
propsOptions 保存的是组件声明的参数名
区分 props 和 attrs
- 如果参数名在
propsOptions中,放入props - 如果参数名不在
propsOptions中,放入attrs
import { reactive } from '@vue/reactivity'
// packages/runtime-core/src/componentProps.ts
import { hasOwn, isArray } from '@vue/shared'
function setFullProps(instance, rawProps, props, attrs) {
const { propsOptions } = instance
if (!rawProps) {
return
}
for (const key in rawProps) {
const value = rawProps[key]
if (hasOwn(propsOptions, key)) {
props[key] = value
}
else {
attrs[key] = value
}
}
}
export function initProps(instance) {
const rawProps = instance.vnode.props
const props = {}
const attrs = {}
setFullProps(instance, rawProps, props, attrs)
instance.props = reactive(props)
instance.attrs = attrs
}
为组件更新时可能需要读取响应式的 props, 所以使用 reactive 包装。
传入 setup
现在需要在组件初始化时调用 initProps,并把 props 和 attrs 传给 setup。
// packages/runtime-core/src/component.ts
import { proxyRefs } from '@vue/reactivity'
import { initProps } from './componentProps'
function createSetupContext(instance) {
return {
attrs: instance.attrs
}
}
export function setupComponent(instance) {
const { type } = instance
initProps(instance)
const setupContext = createSetupContext(instance)
if (isFunction(type.setup)) {
const setupResult = type.setup(instance.props, setupContext)
instance.setupState = proxyRefs(setupResult)
}
instance.render = type.render
}
这样,组件中就可以通过第二个参数访问 attrs:
小结
normalizePropsOptions将数组形式的props转换成对象形式- 创建组件实例时保存
propsOptions initProps遍历原始参数,将参数拆分成props和attrsprops使用reactive包装,支持响应式读取setup接收props和setupContextsetupContext.attrs保存组件未声明的参数