props
props 的作用:允许组件的使用者在外部传递,实现各种各样的功能。
初始化 props
初始化 Props 主要做了 3 件事:
- 设置 props 的值
- 验证 props 合法
- 把 props 变为响应式并且添加到组件实例 instance 上
/**
* 初始化组件
*/
function setupComponent(instance, isSSR = false) {
const { props, children, shapeFlag } = instance.vnode
// 判断是否是一个有状态的组件
const isStateful = shapeFlag & 4
// 初始化 props
initProps(instance, props, isStateful, isSSR)
// 初始化插槽
initSlots(instance, children)
// 设置有状态的组件实例
const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefined
return setupResult
}
/**
* 初始化 Props
* initProps 主要做了 3 件事:
* 1、设置 props 的值
* 2、验证 props 合法
* 3、把 props 变为响应式并且添加到组件实例 instance 上
*/
function initProps(instance, rawProps, isStateful, isSSR = false) {
const props = {}
const attrs = {}
def(attrs, InternalObjectKey, 1)
// 1、设置 props 的值
setFullProps(instance, rawProps, props, attrs)
// 2、验证 props 合法(非生产环境下执行)
if ((process.env.NODE_ENV !== 'production')) {
validateProps(props, instance.type)
}
// 3、把 props 变为响应式并且添加到组件实例 instance 上
// 有状态组件,响应式处理(有状态组件是通过对象方式定义的组件)
if (isStateful) {
instance.props = isSSR ? props : shallowReactive(props)
}
// 函数式组件处理
else {
if (!instance.type.props) {
instance.props = attrs
} else {
instance.props = props
}
}
// 普通属性赋值
instance.attrs = attrs
}
设置 props 的值
/**
* 设置 props 的值 - 对 props 求值,然后把求得的值赋值给 props 对象和 attrs 对象中
* @param {Object} instance - 组件实例
* @param {Object} rawProps - 原始的 props 值,即创建 vnode 过程中传递的 props
* @param {Object} props - 解析后的 props 数据
* @param {Object} attrs - 解析后的普通属性数据
* setFullProps 主要做了 3 件事:
* 1、标准化 props 的配置
* 2、遍历 props 数据求值
* 3、对需要做转换的 props 求值
*/
function setFullProps(instance, rawProps, props, attrs) {
// 1、标准化 props 的配置
const [options, needCastKeys] = normalizePropsOptions(instance.type)
// 2、遍历 props 数据求值
if (rawProps) {
for (const key in rawProps) {
const value = rawProps[key]
// 一些保留的 prop 比如 ref、 key 是不会传递的
if (isReservedProp(key)) continue
// 连字符形式的 props 也转成驼峰形式
let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) {
props[camelKey] = value
}
// 非事件派发相关的,且不在 props 中定义的普通属性用 attrs 保留
else if (!isEmitListener(instance.type, key)) {
attrs[key] = value
}
}
}
// 3、对需要做转换的 props 求值
if (needCastKeys) {
const rawCurrentProps = toRaw(props) // 需要做转换的 props
for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i]
props[key] = resolvePropValue(options, rawCurrentProps, key, rawCurrentProps[key])
}
}
}
标准化 props 的配置
/**
* 标准化 props 的配置
* @description 所有形式的 props 最终都会被标准化为对象形式
*/
function normalizePropsOptions(comp) {
// comp.__props 用于缓存标准化的结果,有缓存,则直接返回
if (comp.__props) {
return comp.__props
}
const raw = comp.props
const normalized = {} // 标准化后的 props 定义
const needCastKeys = [] // 需要转换的 props key
// 处理 mixins和 extends 两个特殊的 prop,因为它们的作用是扩展组件的定义,所以需要对它们定义中的 props 递归执行 normalizePropsOptions
let hasExtends = false
if (!shared.isFunction(comp)) {
const extendProps = (raw) => {
const [props, keys] = normalizePropsOptions(raw)
shared.extend(normalized, props)
if (keys) {
needCastKeys.push(...keys)
}
}
if (comp.extends) {
hasExtends = true
extendProps(comp.extends)
}
if (comp.mixins) {
hasExtends = true
comp.mixins.forEach(extendProps)
}
}
if (!raw && !hasExtends) {
return (comp._props = shared.EMPTY_ARR)
}
// 处理数组形式的 props 定义,如果 props 以数组的形式定义,那么每一项一定要是一个字符串
if (shared.isArray(raw)) {
for (let i = 0; i < rawlength; i++) {
// 非字符串项,报警告
if (!shared.isString(raw[i])) {
warn(/* ... */)
}
// 把字符串变为驼峰形式作为 key,并为每一个 key 创建一个空对象作为值
const normalizedKey = shared.camelize(raw[i])
if (validatePropName(normalizedKey)) {
normalized[normalizedKey] = shared.EMPTY_OBJ
}
}
}
// 处理对象形式的 props 定义
else if (raw) {
if (!shared.isObject(raw)) {
warn(/* ... */)
}
for (const key in raw) {
const normalizedKey = shared.camelize(key)
if (validatePropName(normalizedKey)) {
const opt = raw[key]
// 标准化 prop 的定义格式
const prop = (normalized[normalizedKey] = shared.isArray(opt) || shared.isFunction(opt) ? { type: opt } : opt)
if (prop) {
const booleanlndex = getTypelndex(Boolean, prop.type)
const stringIndex = getTypelndex(String, prop.type)
prop[0/* shouldCast */] = booleanindex > -1
prop[1 /* shouldCastTrue */] = stringIndex < 0 || booleanlndex < stringIndex
//布尔类型和有默认值的 prop 都需要转换
if (booleanIndex > -1 || shared.hasOwn(prop, 'default')) {
needCastKeys.push(normalizedKey)
}
}
}
}
}
const normalizedEntry = [normalized, needCastKeys]
// 缓存标准化结果
comp._props = normalizedEntry
// 返回标准化结果
return normalizedEntry
}
遍历 props 数据求值
// 无额外的函数
对需要做转换的 props 求值
/**
* 处理 Prop 的值 - 对 Props 求值,然后把求得的值赋给 Props 对象的 attrs 对象中
*/
function resolvePropValue(options, props, key, value) {
const opt = options[key]
// 针对两种情况做转换
if (opt != null) {
const hasDefault = hasOwn(opt, 'default')
// 默认值转换,当在 prop 中定义了默认值,且父组件没有传递 prop 时才取默认值
if (hasDefault && value === undefined) {
const defaultValue = opt.default
value = opt.type !== Function && isFunction(defaultValue) ? defaultValue() : defaultValue
}
// 布尔类型转换
if (opt[0 /* shouldCast */]) {
// 如果在 prop 中定义了 Boolean 类型,且父组件没有传递 prop,且没有定义默认值时,直接转换为 false
if (!hasOwn(props, key) && !hasDefault) {
value = false
}
// 其他情况转换为 true
else if (opt[1 /* shouldCastTrue */] &&
(value === '' || value === hyphenate(key))) {
value = true
}
}
}
return value
}
验证 props 合法
/**
* 验证 props 是否合法
*/
function validateProps(props, comp) {
const rawValues = toRaw(props)
const options = normalizePropsOptions(comp)[0]
// 对标准化后的 props 进行遍历,拿到每一个配置 opt,然后执行 validateProp 验证
for (const key in options) {
let opt = options[key]
if (opt == null) continue
validateProp(key, rawValues[key], opt, !hasOwn(rawValues, key))
}
}
/**
* 验证 prop 是否合法
*/
function validateProp(name, value, prop, isAbsent) {
const { type, required, validator } = prop
// 如果配置了 required 但是没有传值,则报警告
if (required && isAbsent) {
warn(/* ... */)
return
}
// 虽然没有值但也没有配置 required,直接返回
if (value == null && !prop.required) return
// 类型检测,只要满足其中一种类型就是合法的,否则报警告
if (type != null && type !== true) {
let isValid = false
const types = isArray(type) ? type : [type]
const expectedTypes = []
}
// 只要指定的类型之一匹配,值就有效
for (let i = 0; i < types.length && !isValid; i++) {
const { valid, expectedType } = assertType(value, types[i])
expectedTypes.push(expectedType || '')
isValid = valid
}
if (!isValid) {
warn(/* ... */)
return
}
// 如果配置了自定义校验器但是不满足校验器的规则,则报警告
if (validator && !validator(value)) {
warn(/* ... */)
}
}
把 props 变为响应式并且添加到组件实例 instance 上
// 无额外函数
问题:
为什么
instance.props
要变成响应式?因为希望在子组件中监听 props 的变化而进行一些操作。
为什么用 shallowReactive API?
因为 props 在更新过程中只会修改最外层属性,shallowReactive 就足够了。
更新 props
/**
* 更新组件
*/
const updateComponent = (nl, n2, parentComponent, optimized) => {
const instance = (n2.component = nl.component)
// 根据新旧子组件 vnode 判断是否需要更新子组件
if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {
instance.next = n2 // 新的子组件 vnode 赋值给 instance.next
// 子组件也可能因为数据变化被添加到更新队列里了,移除它们防止对一个子组件重复更新 invalidateJob(instance.update)
// 执行子组件的副作用渲染函数
instance.update()
}
// 不需要更新,只复制属性
else {
n2.component = n1.component
n2.el = n1.el
}
}
/**
* 更新组件 - 初始化渲染副作用
*/
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// 创建响应式的副作用渲染函数
instance.update = effect(function componentEffect() {
// 渲染组件
if (!instance.isMounted) {
// ...
}
// 更新组件
else {
let { next, vnode } = instance // next 表示新的组件 vnode
// 更新组件 vnode 节点信息
if (next) {
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// 渲染新的子树 vnode
const nextTree = renderComponentRoot(instance)
// 缓存旧的子树 vnode
const prevTree = instance.subTree
// 更新子树 vnode
instance.subTree = nextTree
// 组件更新核心逻辑,根据新旧子树 vnode 做 patch
patch(prevTree, nextTree,
hostParentNode(prevTree.el), // 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点
getNextHostNode(prevTree), // 考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点
instance, parentSuspense, isSVG)
// 缓存更新后的 DOM 节点
next.el = nextTree.el
}
}, prodEffectOptions)
}
/**
* 更新组件 - 在渲染前做一些操作
*/
const updateComponentPreRender = (instance, nextVNode, optimized) => {
nextVNode.component = instance
const prevProps = instance.vnode.props
instance.vnode = nextVNode
instance.next = null
updateProps(instance, nextVNode.props, prevProps, optimized)
updateSlots(instance, nextVNode.children)
}
/**
* 更新 Props - 把父组件渲染时求得的 props 新值更新到子组件实例的 instance.props 中
*/
function updateProps(instance, rawProps, rawPrevProps, optimized) {
const { props, attrs, vnode: { patchFlag } } = instance
const rawCurrentProps = toRaw(props)
const [options] = normalizePropsOptions(instance.type)
if ((optimized || patchFlag > 0) && !(patchFlag & 16/* FULL_PROPS */)) {
// 只更新动态 props 节点
if (patchFlag & 8/* PROPS */) {
const propsToUpdate = instance.vnode.dynamicProps
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const value = rawProps[key]
if (options) {
if (hasOwn(attrs, key)) {
attrs[key] = value
}
else {
const camelizedKey = camelize(key)
props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value)
}
} else {
attrs[key] = value
}
}
}
}
else {
// 全量 props 更新
setFullProps(instance, rawProps, props, attrs)
// 因为新的 props 是动态的,把那些不在新的 props 中但存在于旧的 props 中的值设置为 undefined
let kebabKey
for (const key in rawCurrentProps) {
if (!rawProps || (!hasOwn(rawProps, key) && ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))) {
if (options) {
if (rawPrevProps && (rawPrevProps[key] !== undefined || rawPrevProps[kebabKey] !== undefined)) {
props[key] = resolvePropValue(options, rawProps || EMPTY_OBJ, key, undefined)
}
} else {
delete props[key]
}
}
}
}
if ((process.env.NODE_ENV === 'production') && rawProps) {
validateProps(props, instance.type)
}
}
emit
/**
* 自定义事件的派发
* @param {Object} instance - 执行 $emit 的组件实例
* @param {string} event - 事件名称
* @param {...any} args - 事件传递的参数
*/
function emit(instance, event, ...args) {
const props = instance.vnode.props || EMPTY_OBJ
// 获取事件名称 - 把传递的 event 首字母大写,然后在前面加上 on
let handlerName = `on${capitalize(event)}`
// 根据事件名称在 props 中找到对应的回调函数
let handler = props[handlerName]
// 如果没有对应的回调函数 且 事件是以 update: 开头,则尝试将事件名改为 - 形式再查找对应的回调函数
if (!handler && event.startsWith('update:')) {
handlerName = `on${capitalize(hyphenate(event))}`
handler = props[handlerName]
}
// 如果有对应的回调函数,则执行
if (handler) {
callWithAsyncErrorHandling(handler, instance, 6/* COMPONENT_EVENT_HANDLER */, args)
}
}