Vue3 源码解读系列(十)——props/emit

props

props 的作用:允许组件的使用者在外部传递,实现各种各样的功能。

初始化 props

初始化 Props 主要做了 3 件事:

  1. 设置 props 的值
  2. 验证 props 合法
  3. 把 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 上

// 无额外函数

问题:

  1. 为什么 instance.props 要变成响应式?

    因为希望在子组件中监听 props 的变化而进行一些操作。

  2. 为什么用 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)
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值