props
props 的作用:允许组件的使用者在外部传递,实现各种各样的功能。
初始化 props
初始化 Props 主要做了 3 件事:
- 设置 props 的值
- 验证 props 合法
- 把 props 变为响应式并且添加到组件实例 instance 上
/*** 初始化组件*/
function setupComponent(instance, isSSR = false) {const { props, children, shapeFlag } = instance.vnode// 判断是否是一个有状态的组件const isStateful = shapeFlag & 4// 初始化 propsinitProps(instance, props, isStateful, isSSR)// 初始化插槽initSlots(instance, children)// 设置有状态的组件实例const setupResult = isStateful ? setupStatefulComponent(instance, isSSR) : undefinedreturn 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 camelKeyif (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) // 需要做转换的 propsfor (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.propsconst normalized = {} // 标准化后的 props 定义const needCastKeys = [] // 需要转换的 props key// 处理 mixins和 extends 两个特殊的 prop,因为它们的作用是扩展组件的定义,所以需要对它们定义中的 props 递归执行 normalizePropsOptionslet hasExtends = falseif (!shared.isFunction(comp)) {const extendProps = (raw) => {const [props, keys] = normalizePropsOptions(raw)shared.extend(normalized, props)if (keys) {needCastKeys.push(...keys)}}if (comp.extends) {hasExtends = trueextendProps(comp.extends)}if (comp.mixins) {hasExtends = truecomp.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 > -1prop[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.defaultvalue = opt.type !== Function && isFunction(defaultValue) ? defaultValue() : defaultValue}// 布尔类型转换if (opt[0 /* shouldCast */]) {// 如果在 prop 中定义了 Boolean 类型,且父组件没有传递 prop,且没有定义默认值时,直接转换为 falseif (!hasOwn(props, key) && !hasDefault) {value = false}// 其他情况转换为 trueelse 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) continuevalidateProp(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 = falseconst 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.componentn2.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}// 渲染新的子树 vnodeconst nextTree = renderComponentRoot(instance)// 缓存旧的子树 vnodeconst prevTree = instance.subTree// 更新子树 vnodeinstance.subTree = nextTree// 组件更新核心逻辑,根据新旧子树 vnode 做 patchpatch(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 = instanceconst prevProps = instance.vnode.propsinstance.vnode = nextVNodeinstance.next = nullupdateProps(instance, nextVNode.props, prevProps, optimized)updateSlots(instance, nextVNode.children)
}/*** 更新 Props - 把父组件渲染时求得的 props 新值更新到子组件实例的 instance.props 中*/
function updateProps(instance, rawProps, rawPrevProps, optimized) {const { props, attrs, vnode: { patchFlag } } = instanceconst 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.dynamicPropsfor (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 中的值设置为 undefinedlet kebabKeyfor (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 首字母大写,然后在前面加上 onlet 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)}
}