源码分析:Vue props解析(1)

423 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

props

props是组件间通讯,经常用到的特性,下面我们去从源码角度了解一下原理:

在vue 2.x中,props的逻辑整体上可以分为3个流程,规范化、初始化、更新数据时,下面我们也分成这三个过程去分析:

规范化

在初始化props之前,会对props做一次规范化,规范化过程是在mergeOptions函数中进行的,不清楚mergeOptions逻辑的,可以点击这里

/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  ......

  //  第二个参数如果是函数,则取函数上面的options
  if (typeof child === 'function') {
    child = child.options
  }

  // 规范化props属性
  normalizeProps(child, vm)
  
  ......
  return options
}

可以看到mergeOptions的时候执行了normalizeProps:

**
 * Ensure all props option syntax are normalized into the
 * Object-based format.
 */
function normalizeProps(options: Object, vm: ?Component) {
  // 拿到props,没有这个属性直接返回
  const props = options.props
  if (!props) return
  const res = {}
  let i, val, name
  // props如果是数组,遍历props属性,如果其中的值不是string类型,会报错,否则将名称驼峰化,并赋值为{ type: null }
  if (Array.isArray(props)) {
    i = props.length
    while (i--) {
      val = props[i]
      if (typeof val === 'string') {
        // 名称驼峰化
        name = camelize(val)
        res[name] = { type: null }
      } else if (process.env.NODE_ENV !== 'production') {
        // 不是string类型则报错
        warn('props must be strings when using array syntax.')
      }
    }
  } else if (isPlainObject(props)) {
    // 如果是对象,遍历key值,对key值驼峰化处理,并赋值规范化
    for (const key in props) {
      val = props[key]
      // 名称驼峰化
      name = camelize(key)
      // 对象直接赋值,非对象生成{ type: String } 这种格式
      res[name] = isPlainObject(val)
        ? val
        : { type: val }
    }
  } else if (process.env.NODE_ENV !== 'production') {
    // 不是对象或者数组会报出警告
    warn(
      `Invalid value for option "props": expected an Array or an Object, ` +
      `but got ${toRawType(props)}.`,
      vm
    )
  }
  options.props = res
}

经过上面这源码的解释可能不够直观,举个例子,props可以像如下这样定义:

1.数组形式

export default {
  props: ['name', 'first-name']
}

规范化后:

props: {
  name: {
    type: null
  },
  firstName: {
    type: null
  }
}

数组形式我们平时使用的比较少,但是也是需要清楚的啊!

2.对象形式

export default {
  props: {
    name: String, 
    first-name: {
      type: String,
    }
  }
}

规范化后:

props: {
  name: {
    type: String
  },
  firstName: {
    type: String
  }
}

但对象形式,我们可以定义更多的属性配置,所以推荐更多的使用对象形式的props。

初始化

props的初始化过程发生在initState阶段:

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  ......
}
function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 缓存props键,以便以后的prop更新可以使用数组迭代,而不是动态对象键枚举。
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    // 如果是根实例,执行toggleObserving
    toggleObserving(false)
  }
  for (const key in propsOptions) {
    // 遍历props的键值
    keys.push(key)
    // 校验
    const value = validateProp(key, propsOptions, propsData, vm)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      const hyphenatedKey = hyphenate(key)
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
          vm
        )
      }
      defineReactive(props, key, value, () => {
        if (vm.$parent && !isUpdatingChildComponent) {
          warn(
            `Avoid mutating a prop directly since the value will be ` +
            `overwritten whenever the parent component re-renders. ` +
            `Instead, use a data or computed property based on the prop's ` +
            `value. Prop being mutated: "${key}"`,
            vm
          )
        }
      })
    } else {
      // 响应式
      defineReactive(props, key, value)
    }
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      // 代理
      proxy(vm, `_props`, key)
    }
  }
  toggleObserving(true)
}

可以看到,props初始化的逻辑主要有3个,校验、响应式、以及代理。

下一节我们来继续分析。