Props 作为组件的核心特性之一,也是平时开发 Vue 项目中接触最多的特性之一,它可以让组件的功能变得丰富,也是父子组件通讯的一个渠道

规范化

在初始化props之前首先会对props做一次normalize,发生在mergeOptions时
定义在src/core/util/options.js中

  1. /**
  2. * Merge two option objects into a new one.
  3. * Core utility used in both instantiation and inheritance.
  4. * 处理定义的组件的对象option,然后挂载到组件的实例this.$options中
  5. */
  6. export function mergeOptions (
  7. parent: Object,
  8. child: Object,
  9. vm?: Component
  10. ): Object {
  11. // ...
  12. normalizeProps(child, vm)
  13. // ...
  14. return options
  15. }
  16. /**
  17. * Ensure all props option syntax are normalized into the
  18. * Object-based format.
  19. * 把编写的props转成对象格式 也允许写成数组格式
  20. */
  21. function normalizeProps (options: Object, vm: ?Component) {
  22. const props = options.props
  23. if (!props) return
  24. const res = {}
  25. let i, val, name
  26. // 当props是一个数组,每一个数组元素prop只能是一个string,表示prop的key,转成驼峰格式,prop 的类型为空
  27. if (Array.isArray(props)) {
  28. i = props.length
  29. while (i--) {
  30. val = props[i]
  31. if (typeof val === 'string') {
  32. name = camelize(val)
  33. res[name] = { type: null }
  34. } else if (process.env.NODE_ENV !== 'production') {
  35. warn('props must be strings when using array syntax.')
  36. }
  37. }
  38. } else if (isPlainObject(props)) { // 当props是一个对象,对于props中每个prop的key,会转驼峰格式,而它的value,如果不是一个对象,就把它规范成一个对象
  39. for (const key in props) {
  40. val = props[key]
  41. name = camelize(key)
  42. res[name] = isPlainObject(val)
  43. ? val
  44. : { type: val }
  45. }
  46. } else if (process.env.NODE_ENV !== 'production') { // 如果props既不是数组也不是对象,就抛出一个警告
  47. warn(
  48. `Invalid value for option "props": expected an Array or an Object, ` +
  49. `but got ${toRawType(props)}.`,
  50. vm
  51. )
  52. }
  53. options.props = res
  54. }

例子

  1. export default {
  2. props: ['name', 'nick-name']
  3. }
  4. // 经过normalizeProps后会被规范成
  5. options.props = {
  6. name: { type: null },
  7. nickName: { type: null }
  8. }
  1. export default {
  2. props: {
  3. name: String,
  4. nickName: {
  5. type: Boolean
  6. }
  7. }
  8. }
  9. // 经过normalizeProps后会被规范成
  10. options.props = {
  11. name: { type: String },
  12. nickName: { type: Boolean }
  13. }

由于对象形式的props可以指定每个prop的类型和定义其它的一些属性,推荐用对象形式定义props

初始化

props的初始化主要发生在new Vue中的initState阶段
定义在src/core/instance/state.js中

  1. export function initState (vm: Component) {
  2. vm._watchers = []
  3. const opts = vm.$options
  4. if (opts.props) initProps(vm, opts.props)
  5. // ...
  6. }
  7. function initProps (vm: Component, propsOptions: Object) {
  8. const propsData = vm.$options.propsData || {}
  9. const props = vm._props = {}
  10. // cache prop keys so that future props updates can iterate using Array
  11. // instead of dynamic object key enumeration.
  12. const keys = vm.$options._propKeys = []
  13. const isRoot = !vm.$parent
  14. // root instance props should be converted
  15. if (!isRoot) {
  16. toggleObserving(false)
  17. }
  18. for (const key in propsOptions) {
  19. keys.push(key)
  20. // 校验
  21. // propsOptions 定义的props在规范后生成的options.props对象
  22. // propsData 父组件传递的prop数据
  23. const value = validateProp(key, propsOptions, propsData, vm)
  24. /* istanbul ignore else */
  25. if (process.env.NODE_ENV !== 'production') {
  26. const hyphenatedKey = hyphenate(key)
  27. if (isReservedAttribute(hyphenatedKey) ||
  28. config.isReservedAttr(hyphenatedKey)) {
  29. warn(
  30. `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
  31. vm
  32. )
  33. }
  34. // 响应式
  35. // 把prop变成响应式
  36. defineReactive(props, key, value, () => {
  37. if (!isRoot && !isUpdatingChildComponent) {
  38. warn(
  39. `Avoid mutating a prop directly since the value will be ` +
  40. `overwritten whenever the parent component re-renders. ` +
  41. `Instead, use a data or computed property based on the prop's ` +
  42. `value. Prop being mutated: "${key}"`,
  43. vm
  44. )
  45. }
  46. })
  47. } else {
  48. defineReactive(props, key, value)
  49. }
  50. // static props are already proxied on the component's prototype
  51. // during Vue.extend(). We only need to proxy props defined at
  52. // instantiation here.
  53. // 代理
  54. if (!(key in vm)) {
  55. proxy(vm, `_props`, key)
  56. }
  57. }
  58. toggleObserving(true)
  59. }

initProps主要做 3 件事情:校验、响应式和代理

检验

检查一下传递的数据是否满足prop的定义规范
validateProp定义在src/core/util/props.js中

  1. export function validateProp (
  2. key: string,
  3. propOptions: Object,
  4. propsData: Object,
  5. vm?: Component
  6. ): any {
  7. const prop = propOptions[key]
  8. const absent = !hasOwn(propsData, key)
  9. let value = propsData[key]
  10. // boolean casting
  11. // 1.处理Boolean类型的数据
  12. const booleanIndex = getTypeIndex(Boolean, prop.type) // 拿到的是有Boolean构造的索引
  13. if (booleanIndex > -1) {
  14. // 判断如果父组件没有传递这个prop数据并且没有设置default的情况,则value值为false
  15. if (absent && !hasOwn(prop, 'default')) {
  16. value = false
  17. } else if (value === '' || value === hyphenate(key)) {
  18. // only cast empty string / same name to boolean if
  19. // boolean has higher priority
  20. // 拿到匹配String构造的索引
  21. const stringIndex = getTypeIndex(String, prop.type)
  22. // 有值
  23. if (stringIndex < 0 || booleanIndex < stringIndex) {
  24. value = true
  25. }
  26. }
  27. }
  28. // check default value
  29. // 2.处理默认数据
  30. if (value === undefined) { // 父组件没有传这个prop
  31. // getPropDefaultValue去获取默认值
  32. value = getPropDefaultValue(vm, prop, key)
  33. // since the default value is a fresh copy,
  34. // make sure to observe it.
  35. const prevShouldObserve = shouldObserve
  36. toggleObserving(true)
  37. observe(value)
  38. toggleObserving(prevShouldObserve)
  39. }
  40. // 在开发环境且非weex的某种环境下
  41. if (
  42. process.env.NODE_ENV !== 'production' &&
  43. // skip validation for weex recycle-list child component props
  44. !(__WEEX__ && isObject(value) && ('@binding' in value))
  45. ) {
  46. // 3.prop断言
  47. assertProp(prop, key, value, vm, absent)
  48. }
  49. // 返回prop的值
  50. return value
  51. }

处理Boolean类型的数据

getTypeIndex就是找到type和expectedTypes匹配的索引并返回

  1. /**
  2. * Use function string name to check built-in types,
  3. * because a simple equality check will fail when running
  4. * across different vms / iframes.
  5. */
  6. function getType (fn) {
  7. const match = fn && fn.toString().match(functionTypeCheckRE)
  8. return match ? match[1] : ''
  9. }
  10. function isSameType (a, b) {
  11. return getType(a) === getType(b)
  12. }
  13. function getTypeIndex (type, expectedTypes): number {
  14. if (!Array.isArray(expectedTypes)) {
  15. return isSameType(expectedTypes, type) ? 0 : -1
  16. }
  17. for (let i = 0, len = expectedTypes.length; i < len; i++) {
  18. if (isSameType(expectedTypes[i], type)) {
  19. return i
  20. }
  21. }
  22. return -1
  23. }

prop类型定义时可以是某个原生构造函数,也可以是原生构造函数的数组
比如

  1. export default {
  2. props: {
  3. name: String,
  4. value: [String, Boolean]
  5. }
  6. }

如果expectedTypes是单个构造函数就执行isSameType去判断是否是一个类型
如果是数组那就遍历这个数组找到第一个同类型的,返回它的索引
例子说明处理Boolean类型的数据

  1. // 组件Student
  2. export default {
  3. name: String,
  4. nickName: [Boolean, String]
  5. }
  1. /* 父组件引入这个组件 */
  2. <template>
  3. <div>
  4. <student name="Kate" nick-name></student>
  5. </div>
  6. </template>
  7. /* 没有写属性的值,满足value==='' */
  8. /* 或者是 */
  9. <template>
  10. <div>
  11. <student name="Kate" nick-name="nick-name"></student>
  12. </div>
  13. </template>
  14. /* 满足value===hyphenate(key) */

nickName这个prop的类型是Boolean或者是String,并且满足booleanIndex < stringIndex,所以对nickName这个prop的value为true

处理默认数据

getPropDefaultValue

  1. /**
  2. * Get the default value of a prop.
  3. */
  4. function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
  5. // no default, return undefined
  6. // 没有定义default属性返回undefined
  7. if (!hasOwn(prop, 'default')) {
  8. return undefined
  9. }
  10. const def = prop.default
  11. // warn against non-factory defaults for Object & Array
  12. // 开发环境下对prop的默认值是否为对象或者数组类型进行判断
  13. // 如果是的话会报警告,因为对象和数组类型的prop它们的默认值必须要返回一个工厂函数
  14. if (process.env.NODE_ENV !== 'production' && isObject(def)) {
  15. warn(
  16. 'Invalid default value for prop "' + key + '": ' +
  17. 'Props with type Object/Array must use a factory function ' +
  18. 'to return the default value.',
  19. vm
  20. )
  21. }
  22. // the raw prop value was also undefined from previous render,
  23. // return previous default value to avoid unnecessary watcher trigger
  24. // 如果上一次组件渲染父组件传递的prop的值是undefined,则直接返回上一次的默认值vm._props[key],这样可以避免触发不必要的watcher函数
  25. if (vm && vm.$options.propsData &&
  26. vm.$options.propsData[key] === undefined &&
  27. vm._props[key] !== undefined
  28. ) {
  29. return vm._props[key]
  30. }
  31. // call factory function for non-Function types
  32. // a value is Function if its prototype is function even across different execution context
  33. // 判断def如果是工厂函数且prop的类型不是Function时返回工厂函数的返回值,否则直接返回def
  34. return typeof def === 'function' && getType(prop.type) !== 'Function'
  35. ? def.call(vm)
  36. : def
  37. }

除了Boolean类型的数据,其余没有设置default属性的prop默认值都是undefined

prop断言

assertProp做属性断言

  1. /**
  2. * Assert whether a prop is valid.
  3. * 断言这个prop是否合法
  4. */
  5. function assertProp (
  6. prop: PropOptions,
  7. name: string,
  8. value: any,
  9. vm: ?Component,
  10. absent: boolean
  11. ) {
  12. // 如果prop定义了required属性但父组件没有传递这个prop数据的话会报一个警告
  13. if (prop.required && absent) {
  14. warn(
  15. 'Missing required prop: "' + name + '"',
  16. vm
  17. )
  18. return
  19. }
  20. // 如果value为空且prop没有定义required属性则直接返回
  21. if (value == null && !prop.required) {
  22. return
  23. }
  24. // 去对prop的类型做校验
  25. let type = prop.type // 先拿到peop中定义的类型type
  26. let valid = !type || type === true
  27. const expectedTypes = []
  28. if (type) {
  29. // 如果不是数组,转成一个类型数组
  30. if (!Array.isArray(type)) {
  31. type = [type]
  32. }
  33. // 依次遍历这个数组,执行assertType(value, type[i], vm)去获取断言的结果
  34. for (let i = 0; i < type.length && !valid; i++) {
  35. const assertedType = assertType(value, type[i], vm)
  36. expectedTypes.push(assertedType.expectedType || '')
  37. valid = assertedType.valid
  38. }
  39. }
  40. const haveExpectedTypes = expectedTypes.some(t => t)
  41. // 循环结束后valid仍然为false,说明prop的值value与prop定义的类型都不匹配,就输入警告信息
  42. if (!valid && haveExpectedTypes) {
  43. warn(
  44. getInvalidTypeMessage(name, value, expectedTypes),
  45. vm
  46. )
  47. return
  48. }
  49. // 当prop自己定义了validator自定义校验器,则执行validator校验器方法,
  50. const validator = prop.validator
  51. if (validator) {
  52. // 如果校验不通过则输出警告信息
  53. if (!validator(value)) {
  54. warn(
  55. 'Invalid prop: custom validator check failed for prop "' + name + '".',
  56. vm
  57. )
  58. }
  59. }
  60. }

assertType

  1. const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol|BigInt)$/
  2. function assertType (value: any, type: Function, vm: ?Component): {
  3. valid: boolean;
  4. expectedType: string;
  5. } {
  6. let valid
  7. // 获取prop期望的类型
  8. const expectedType = getType(type)
  9. // 根据几种不同的情况对比prop的值value是否和expectedType匹配
  10. if (simpleCheckRE.test(expectedType)) {
  11. const t = typeof value
  12. valid = t === expectedType.toLowerCase()
  13. // for primitive wrapper objects
  14. if (!valid && t === 'object') {
  15. valid = value instanceof type
  16. }
  17. } else if (expectedType === 'Object') {
  18. valid = isPlainObject(value)
  19. } else if (expectedType === 'Array') {
  20. valid = Array.isArray(value)
  21. } else {
  22. try {
  23. valid = value instanceof type
  24. } catch (e) {
  25. warn('Invalid prop type: "' + String(type) + '" is not a constructor', vm);
  26. valid = false;
  27. }
  28. }
  29. // 返回匹配结果
  30. return {
  31. valid,
  32. expectedType
  33. }
  34. }

响应式

  1. /* istanbul ignore else */
  2. if (process.env.NODE_ENV !== 'production') {
  3. const hyphenatedKey = hyphenate(key)
  4. if (isReservedAttribute(hyphenatedKey) ||
  5. config.isReservedAttr(hyphenatedKey)) {
  6. warn(
  7. `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
  8. vm
  9. )
  10. }
  11. defineReactive(props, key, value, () => {
  12. if (!isRoot && !isUpdatingChildComponent) {
  13. warn(
  14. `Avoid mutating a prop directly since the value will be ` +
  15. `overwritten whenever the parent component re-renders. ` +
  16. `Instead, use a data or computed property based on the prop's ` +
  17. `value. Prop being mutated: "${key}"`,
  18. vm
  19. )
  20. }
  21. })
  22. } else {
  23. defineReactive(props, key, value)
  24. }

在开发环境中会校验prop的key是否是HTML的保留属性
在defineReactive时会添加一个自定义的setter,当直接对prop赋值时会输出警告
关于 prop 的响应式有一点不同的是当 vm 是非根实例的时候,会先执行 toggleObserving(false),它的目的是为了响应式的优化

代理

经过响应式处理后会把prop的值添加到vm.props中
比如key为name的prop,它的值保存在vm._props.name中
在组件中可以通过this.name访问这个prop,这就是代理做的事情

  1. // static props are already proxied on the component's prototype
  2. // during Vue.extend(). We only need to proxy props defined at
  3. // instantiation here.
  4. // 代理
  5. if (!(key in vm)) {
  6. proxy(vm, `_props`, key)
  7. }

通过proxy函数实现

  1. const sharedPropertyDefinition = {
  2. enumerable: true,
  3. configurable: true,
  4. get: noop,
  5. set: noop
  6. }
  7. export function proxy (target: Object, sourceKey: string, key: string) {
  8. // 当访问this.name时就相当于访问this._props.name
  9. sharedPropertyDefinition.get = function proxyGetter () {
  10. return this[sourceKey][key]
  11. }
  12. sharedPropertyDefinition.set = function proxySetter (val) {
  13. this[sourceKey][key] = val
  14. }
  15. Object.defineProperty(target, key, sharedPropertyDefinition)
  16. }

对于非根实例的子组件proxy的代理发生在Vue.extend阶段
定义在src/core/global-api/extend.js中

  1. /**
  2. * Class inheritance
  3. */
  4. Vue.extend = function (extendOptions: Object): Function {
  5. // ...
  6. const Sub = function VueComponent (options) {
  7. this._init(options)
  8. }
  9. // ...
  10. // For props and computed properties, we define the proxy getters on
  11. // the Vue instances at extension time, on the extended prototype. This
  12. // avoids Object.defineProperty calls for each instance created.
  13. if (Sub.options.props) {
  14. initProps(Sub)
  15. }
  16. if (Sub.options.computed) {
  17. initComputed(Sub)
  18. }
  19. // ...
  20. return Sub
  21. }
  22. function initProps (Comp) {
  23. const props = Comp.options.props
  24. for (const key in props) {
  25. proxy(Comp.prototype, `_props`, key)
  26. }
  27. }

这么做的好处是不用为每个组件实例都做一层proxy,是一种优化手段

Props更新

当父组件传递给子组件的 props 值变化,子组件对应的值也会改变,同时会触发子组件的重新渲染

子组件props更新

prop 数据的值变化在父组件,我们知道在父组件的 render 过程中会访问到这个 prop 数据,所以当 prop 数据变化一定会触发父组件的重新渲染,那么重新渲染是如何更新子组件对应的 prop 的值呢?
在父组件重新渲染的最后,会执行 patch 过程,进而执行 patchVnode 函数,patchVnode 通常是一个递归过程,当它遇到组件 vnode 的时候,会执行组件更新过程的 prepatch 钩子函数
定义在src/core/vdom/patch.js中

  1. function patchVnode (
  2. oldVnode,
  3. vnode,
  4. insertedVnodeQueue,
  5. ownerArray,
  6. index,
  7. removeOnly
  8. ) {
  9. // ...
  10. let i
  11. const data = vnode.data
  12. if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  13. i(oldVnode, vnode)
  14. }
  15. // ...
  16. }

prepatch函数定义在src/core/vdom/create-component.js中

  1. prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  2. const options = vnode.componentOptions
  3. const child = vnode.componentInstance = oldVnode.componentInstance
  4. // 更新props
  5. updateChildComponent(
  6. child,
  7. options.propsData, // updated props 父组件的propData
  8. options.listeners, // updated listeners
  9. vnode, // new parent vnode
  10. options.children // new children
  11. )
  12. },

那么为什么 vnode.componentOptions.propsData 就是父组件传递给子组件的 prop 数据呢(这个也同样解释了第一次渲染的 propsData 来源)?
原来在组件的 render 过程中,对于组件节点会通过 createComponent 方法来创建组件 vnode

  1. export function createComponent (
  2. Ctor: Class<Component> | Function | Object | void,
  3. data: ?VNodeData,
  4. context: Component,
  5. children: ?Array<VNode>,
  6. tag?: string
  7. ): VNode | Array<VNode> | void {
  8. // ...
  9. // extract props
  10. const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  11. // ...
  12. const vnode = new VNode(
  13. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  14. data, undefined, undefined, undefined, context,
  15. { Ctor, propsData, listeners, tag, children },
  16. asyncFactory
  17. )
  18. // ...
  19. return vnode
  20. }

在创建组件 vnode 的过程中,首先从 data 中提取出 propData,然后在 new VNode 的时候,作为第七个参数 VNodeComponentOptions 中的一个属性传入,所以可以通过 vnode.componentOptions.propsData 拿到 prop 数据
接着看updateChildComponent函数
定义在src/core/instance/lifecycle.js中

  1. export function updateChildComponent (
  2. vm: Component, // 子组件实例
  3. propsData: ?Object, // 父组件传递的props数据
  4. listeners: ?Object,
  5. parentVnode: MountedComponentVNode,
  6. renderChildren: ?Array<VNode>
  7. ) {
  8. // ...
  9. // update props
  10. if (propsData && vm.$options.props) {
  11. toggleObserving(false)
  12. const props = vm._props // vm._props指向的是子组件的props值
  13. const propKeys = vm.$options._propKeys || [] // propKeys是在之前initProps过程中缓存的子组件中定义的所有prop的key
  14. // 遍历propKeys
  15. for (let i = 0; i < propKeys.length; i++) {
  16. const key = propKeys[i]
  17. const propOptions: any = vm.$options.props // wtf flow?
  18. props[key] = validateProp(key, propOptions, propsData, vm) // 重新验证和计算新的prop数据,更新vm._props,也就是子组件的props
  19. }
  20. toggleObserving(true)
  21. // keep a copy of raw propsData
  22. vm.$options.propsData = propsData
  23. }
  24. // ...
  25. }

子组件重新渲染

子组件的重新渲染有 2 种情况,一个是 prop 值被修改,另一个是对象类型的 prop 内部属性的变化

prop 值被修改的情况

当执行 props[key] = validateProp(key, propOptions, propsData, vm) 更新子组件 prop 的时候,会触发 prop 的 setter 过程,只要在渲染子组件的时候访问过这个 prop 值,那么根据响应式原理,就会触发子组件的重新渲染

对象类型的 prop 的内部属性发生变化时

这个时候其实并没有触发子组件 prop 的更新。但是在子组件的渲染过程中,访问过这个对象 prop,所以这个对象 prop 在触发 getter 的时候会把子组件的 render watcher 收集到依赖中,然后当父组件更新这个对象 prop 的某个属性的时候,会触发 setter 过程,也就会通知子组件 render watcher 的 update,进而触发子组件的重新渲染

toggleObserving

定义在src/core/observer/index.js中

  1. /**
  2. * In some cases we may want to disable observation inside a component's
  3. * update computation.
  4. */
  5. export let shouldObserve: boolean = true // 控制在observe的过程中是否需要把当前值变成一个Observer对象
  6. export function toggleObserving (value: boolean) {
  7. shouldObserve = value
  8. }

那么为什么在 props 的初始化和更新过程中,多次执行 toggleObserving(false) 呢

在initProps过程中

  1. function initProps (vm: Component, propsOptions: Object) {
  2. // ...
  3. // root instance props should be converted
  4. if (!isRoot) {
  5. toggleObserving(false)
  6. }
  7. for (const key in propsOptions) {
  8. keys.push(key)
  9. const value = validateProp(key, propsOptions, propsData, vm)
  10. /* istanbul ignore else */
  11. if (process.env.NODE_ENV !== 'production') {
  12. // ...
  13. } else {
  14. defineReactive(props, key, value)
  15. }
  16. // ...
  17. }
  18. toggleObserving(true)
  19. }

对于非根实例的情况会执行toggleObserving(false),然后对于每一个prop值去执行defineReactive(props, key, value)把它变成响应式

defineReactive中

  1. export function defineReactive (
  2. obj: Object,
  3. key: string,
  4. val: any,
  5. customSetter?: ?Function,
  6. shallow?: boolean
  7. ) {
  8. // ...
  9. let childOb = !shallow && observe(val)
  10. Object.defineProperty(obj, key, {
  11. enumerable: true,
  12. configurable: true,
  13. get: function reactiveGetter () {
  14. // ...
  15. },
  16. set: function reactiveSetter (newVal) {
  17. // ...
  18. }
  19. })
  20. }

对于值 val 会执行 observe 函数,然后遇到 val 是对象或者数组的情况会递归执行 defineReactive 把它们的子属性都变成响应式的,但是由于 shouldObserve 的值变成了 false,这个递归过程被省略了。为什么会这样呢?
对于对象的 prop 值,子组件的 prop 值始终指向父组件的 prop 值,只要父组件的 prop 值变化,就会触发子组件的重新渲染,所以这个 observe 过程是可以省略的
最后再执行 toggleObserving(true) 恢复 shouldObserve 为 true

在 validateProp 的过程中

  1. export function validateProp (
  2. key: string,
  3. propOptions: Object,
  4. propsData: Object,
  5. vm?: Component
  6. ): any {
  7. // ...
  8. // check default value
  9. if (value === undefined) {
  10. value = getPropDefaultValue(vm, prop, key)
  11. // since the default value is a fresh copy,
  12. // make sure to observe it.
  13. const prevShouldObserve = shouldObserve
  14. toggleObserving(true)
  15. observe(value)
  16. toggleObserving(prevShouldObserve)
  17. }
  18. // ...
  19. }

这种是父组件没有传递 prop 值对默认值的处理逻辑,因为这个值是一个拷贝,所以需要 toggleObserving(true),然后执行 observe(value) 把值变成响应式

在 updateChildComponent 过程中

  1. export function updateChildComponent (
  2. vm: Component,
  3. propsData: ?Object,
  4. listeners: ?Object,
  5. parentVnode: MountedComponentVNode,
  6. renderChildren: ?Array<VNode>
  7. ) {
  8. // ...
  9. // update props
  10. if (propsData && vm.$options.props) {
  11. toggleObserving(false)
  12. const props = vm._props
  13. const propKeys = vm.$options._propKeys || []
  14. for (let i = 0; i < propKeys.length; i++) {
  15. const key = propKeys[i]
  16. const propOptions: any = vm.$options.props // wtf flow?
  17. props[key] = validateProp(key, propOptions, propsData, vm)
  18. }
  19. toggleObserving(true)
  20. // keep a copy of raw propsData
  21. vm.$options.propsData = propsData
  22. }
  23. // ...
  24. }

和 initProps 的逻辑一样,不需要对引用类型 props 递归做响应式处理,所以也需要 toggleObserving(false)