Appearance
Props (v2.6.11)
Props 作为组件的核心特性之一,也是我们平时开发 Vue 项目中接触最多的特性之一,它可以让组件的功能变得丰富,也是父子组件通讯的一个渠道。那么它的实现原理是怎样的,我们来一探究竟。
规范化
在初始化 props 之前,首先会对 props 做一次 normalize,它发生在 mergeOptions 的时候,在 src/core/util/options.js 中:
js
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
// ...
normalizeProps(child, vm)
// ...
}
function normalizeProps (options: Object, vm: ?Component) {
const props = options.props
if (!props) return
const res = {}
let i, val, name
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') {
warn('props must be strings when using array syntax.')
}
}
} else if (isPlainObject(props)) {
for (const key in props) {
val = props[key]
name = camelize(key)
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
}
合并配置我们在组件化章节讲过,它主要就是处理我们定义组件的对象 option,然后挂载到组件的实例 this.$options 中。
我们接下来重点看 normalizeProps 的实现,其实这个函数的主要目的就是把我们编写的 props 转成对象格式,因为实际上 props 除了对象格式,还允许写成数组格式。
当 props 是一个数组,每一个数组元素 prop 只能是一个 string,表示 prop 的 key,转成驼峰格式,prop 的类型为空。
当 props 是一个对象,对于 props 中每个 prop 的 key,我们会转驼峰格式,而它的 value,如果不是一个对象,我们就把它规范成一个对象。
如果 props 既不是数组也不是对象,就抛出一个警告。
举个例子:
js
export default {
props: ['name', 'nick-name']
}
经过 normalizeProps 后,会被规范成:
js
options.props = {
name: { type: null },
nickName: { type: null }
}
export default {
props: {
name: String,
nickName: {
type: Boolean
}
}
}
经过 normalizeProps 后,会被规范成:
js
options.props = {
name: { type: String },
nickName: { type: Boolean }
}
由于对象形式的 props 可以指定每个 prop 的类型和定义其它的一些属性,推荐用对象形式定义 props。
初始化
Props 的初始化主要发生在 new Vue 中的 initState 阶段,在 src/core/instance/state.js 中:
js
export function initState (vm: Component) {
// ....
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.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
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 (!isRoot && !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)
}
initProps 主要做 3 件事情:校验、响应式和代理。
校验
校验的逻辑很简单,遍历 propsOptions,执行 validateProp(key, propsOptions, propsData, vm) 方法。
这里的 propsOptions 就是我们定义的 props 在规范后生成的 options.props 对象;
propsData 是从父组件传递的 prop 数据。
所谓校验的目的就是检查一下我们传递的数据是否满足 prop的定义规范。
再来看一下 validateProp 方法,它定义在 src/core/util/props.js 中:
js
export function validateProp (
key: string,
propOptions: Object,
propsData: Object,
vm?: Component
): any {
const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// boolean casting
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
return value
}
validateProp 主要就做 3 件事情:
- 处理 Boolean 类型的数据
- 处理默认数据,prop 断言
- 并最终返回 prop 的值。
先来看 Boolean 类型数据的处理逻辑:
js
const prop = propOptions[key]
const absent = !hasOwn(propsData, key)
let value = propsData[key]
// boolean casting
const booleanIndex = getTypeIndex(Boolean, prop.type)
if (booleanIndex > -1) {
if (absent && !hasOwn(prop, 'default')) {
value = false
} else if (value === '' || value === hyphenate(key)) {
// only cast empty string / same name to boolean if
// boolean has higher priority
const stringIndex = getTypeIndex(String, prop.type)
if (stringIndex < 0 || booleanIndex < stringIndex) {
value = true
}
}
}
先通过 const booleanIndex = getTypeIndex(Boolean, prop.type) 来判断 prop 的定义是否是 Boolean 类型的。
js
function getType (fn) {
const match = fn && fn.toString().match(/^\s*function (\w+)/)
return match ? match[1] : ''
}
function isSameType (a, b) {
return getType(a) === getType(b)
}
function getTypeIndex (type, expectedTypes): number {
if (!Array.isArray(expectedTypes)) {
return isSameType(expectedTypes, type) ? 0 : -1
}
for (let i = 0, len = expectedTypes.length; i < len; i++) {
if (isSameType(expectedTypes[i], type)) {
return i
}
}
return -1
}
getTypeIndex 函数就是找到 type 和 expectedTypes 匹配的索引并返回。
prop 类型定义的时候可以是某个原生构造函数,也可以是原生构造函数的数组,比如:
js
export default {
props: {
name: String,
value: [String, Boolean]
}
}
如果 expectedTypes 是单个构造函数,就执行 isSameType 去判断是否是同一个类型;如果是数组,那么就遍历这个数组,找到第一个同类型的,返回它的索引。
回到 validateProp 函数,通过 const booleanIndex = getTypeIndex(Boolean, prop.type) 得到 booleanIndex,如果 prop.type 是一个 Boolean 类型,则通过 absent && !hasOwn(prop, 'default') 来判断如果父组件没有传递这个 prop 数据并且没有设置 default 的情况,则 value 为 false。
接着判断value === '' || value === hyphenate(key) 的情况,如果满足则先通过 const stringIndex = getTypeIndex(String, prop.type) 获取匹配 String 类型的索引,然后判断 stringIndex < 0 || booleanIndex < stringIndex 的值来决定 value 的值是否为 true。这块逻辑稍微有点绕,我们举 2 个例子来说明:
例如你定义一个组件 Student:
js
export default {
name: String,
nickName: [Boolean, String]
}
然后在父组件中引入这个组件:
html
<template>
<div>
<student name="Kate" nick-name></student>
</div>
</template>
或者是:
html
<template>
<div>
<student name="Kate" nick-name="nick-name"></student>
</div>
</template>
第一种情况没有写属性的值,满足 value === '',第二种满足 value === hyphenate(key) 的情况,另外 nickName 这个 prop 的类型是 Boolean 或者是 String,并且满足 booleanIndex < stringIndex,所以对 nickName 这个 prop 的 value 为 true。
接下来看一下默认数据处理逻辑:
js
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
当 value 的值为 undefined 的时候,说明父组件根本就没有传这个 prop,那么我们就需要通过 getPropDefaultValue(vm, prop, key) 获取这个 prop 的默认值。我们这里只关注 getPropDefaultValue 的实现,toggleObserving 和 observe 的作用我们之后会说。
js
function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any {
// no default, return undefined
if (!hasOwn(prop, 'default')) {
return undefined
}
const def = prop.default
// warn against non-factory defaults for Object & Array
if (process.env.NODE_ENV !== 'production' && isObject(def)) {
warn(
'Invalid default value for prop "' + key + '": ' +
'Props with type Object/Array must use a factory function ' +
'to return the default value.',
vm
)
}
// the raw prop value was also undefined from previous render,
// return previous default value to avoid unnecessary watcher trigger
if (vm && vm.$options.propsData &&
vm.$options.propsData[key] === undefined &&
vm._props[key] !== undefined
) {
return vm._props[key]
}
// call factory function for non-Function types
// a value is Function if its prototype is function even across different execution context
return typeof def === 'function' && getType(prop.type) !== 'Function'
? def.call(vm)
: def
}
检测如果 prop 没有定义 default 属性,那么返回 undefined,通过这块逻辑我们知道除了 Boolean 类型的数据,其余没有设置 default 属性的 prop 默认值都是 undefined。
接着是开发环境下对 prop 的默认值是否为对象或者数组类型的判断,如果是的话会报警告,因为对象和数组类型的 prop,他们的默认值必须要返回一个工厂函数。
接下来的判断是如果上一次组件渲染父组件传递的 prop 的值是 undefined,则直接返回 上一次的默认值 vm._props[key],这样可以避免触发不必要的 watcher 的更新。
最后就是判断 def 如果是工厂函数且 prop 的类型不是 Function 的时候,返回工厂函数的返回值,否则直接返回 def。
至此,我们讲完了 validateProp 函数的 Boolean 类型数据的处理逻辑和默认数据处理逻辑,最后来看一下 prop 断言逻辑。
js
if (
process.env.NODE_ENV !== 'production' &&
// skip validation for weex recycle-list child component props
!(__WEEX__ && isObject(value) && ('@binding' in value))
) {
assertProp(prop, key, value, vm, absent)
}
在开发环境且非 weex 的某种环境下,执行 assertProp 做属性断言。
js
function assertProp (
prop: PropOptions,
name: string,
value: any,
vm: ?Component,
absent: boolean
) {
if (prop.required && absent) {
warn(
'Missing required prop: "' + name + '"',
vm
)
return
}
if (value == null && !prop.required) {
return
}
let type = prop.type
let valid = !type || type === true
const expectedTypes = []
if (type) {
if (!Array.isArray(type)) {
type = [type]
}
for (let i = 0; i < type.length && !valid; i++) {
const assertedType = assertType(value, type[i])
expectedTypes.push(assertedType.expectedType || '')
valid = assertedType.valid
}
}
if (!valid) {
warn(
getInvalidTypeMessage(name, value, expectedTypes),
vm
)
return
}
const validator = prop.validator
if (validator) {
if (!validator(value)) {
warn(
'Invalid prop: custom validator check failed for prop "' + name + '".',
vm
)
}
}
}
assertProp 函数的目的是断言这个 prop 是否合法。
首先判断如果 prop 定义了 required 属性但父组件没有传递这个 prop 数据的话会报一个警告。
接着判断如果 value 为空且 prop 没有定义 required 属性则直接返回。
然后再去对 prop 的类型做校验,先是拿到 prop 中定义的类型 type,并尝试把它转成一个类型数组,然后依次遍历这个数组,执行 assertType(value, type[i]) 去获取断言的结果,直到遍历完成或者是 valid 为 true 的时候跳出循环。
js
const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
function assertType (value: any, type: Function): {
valid: boolean;
expectedType: string;
} {
let valid
const expectedType = getType(type)
if (simpleCheckRE.test(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type
}
} else if (expectedType === 'Object') {
valid = isPlainObject(value)
} else if (expectedType === 'Array') {
valid = Array.isArray(value)
} else {
valid = value instanceof type
}
return {
valid,
expectedType
}
}
assertType 的逻辑很简单,先通过 getType(type) 获取 prop 期望的类型 expectedType,然后再去根据几种不同的情况对比 prop 的值 value 是否和 expectedType 匹配,最后返回匹配的结果。
如果循环结束后 valid 仍然为 false,那么说明 prop 的值 value 与 prop 定义的类型都不匹配,那么就会输出一段通过 getInvalidTypeMessage(name, value, expectedTypes) 生成的警告信息,就不细说了。
最后判断当 prop 自己定义了 validator 自定义校验器,则执行 validator 校验器方法,如果校验不通过则输出警告信息。
响应式
回到 initProps 方法,当我们通过 const value = validateProp(key, propsOptions, propsData, vm) 对 prop 做验证并且获取到 prop 的值后,接下来需要通过 defineReactive 把 prop 变成响应式。
defineReactive 我们之前已经介绍过,这里要注意的是,在开发环境中我们会校验 prop 的 key 是否是 HTML 的保留属性,并且在 defineReactive 的时候会添加一个自定义 setter,当我们直接对 prop 赋值的时候会输出警告:
js
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 (!isRoot && !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
)
}
})
}
关于 prop 的响应式有一点不同的是当 vm 是非根实例的时候,会先执行 toggleObserving(false),它的目的是为了响应式的优化,我们先跳过,之后会详细说明。
代理
在经过响应式处理后,我们会把 prop 的值添加到 vm._props 中,比如 key 为 name 的 prop,它的值保存在 vm._props.name 中,但是我们在组件中可以通过 this.name 访问到这个 prop,这就是代理做的事情。
js
// 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)
}
通过 proxy 函数实现了上述需求。
js
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
当访问 this.name 的时候就相当于访问 this._props.name。
其实对于非根实例的子组件而言,prop 的代理发生在 Vue.extend 阶段,在 src/core/global-api/extend.js 中:
js
Vue.extend = function (extendOptions: Object): Function {
// ...
const Sub = function VueComponent (options) {
this._init(options)
}
// ...
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// ...
return Sub
}
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
这么做的好处是不用为每个组件实例都做一层 proxy,是一种优化手段。
Props 更新
我们知道,当父组件传递给子组件的 props 值变化,子组件对应的值也会改变,同时会触发子组件的重新渲染。那么接下来我们就从源码角度来分析这两个过程。
子组件 props 更新
首先,prop 数据的值变化在父组件,我们知道在父组件的 render 过程中会访问到这个 prop 数据,所以当 prop 数据变化一定会触发父组件的重新渲染,那么重新渲染是如何更新子组件对应的 prop 的值呢?
在父组件重新渲染的最后,会执行 patch 过程,进而执行 patchVnode 函数,patchVnode 通常是一个递归过程,当它遇到组件 vnode 的时候,会执行组件更新过程的 prepatch 钩子函数,在 src/core/vdom/patch.js 中:
js
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
// ...
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
// ...
}
prepatch 函数定义在 src/core/vdom/create-component.js 中:
js
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
}
内部会调用 updateChildComponent 方法来更新 props,注意第二个参数就是父组件的 propData;
那么为什么 vnode.componentOptions.propsData 就是父组件传递给子组件的 prop 数据呢(这个也同样解释了第一次渲染的 propsData 来源)?
原来在组件的 render 过程中,对于组件节点会通过 createComponent 方法来创建组件 vnode:
js
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
// ...
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// ...
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// ...
return vnode
}
在创建组件 vnode 的过程中,首先从 data 中提取出 propData,然后在 new VNode 的时候,作为第七个参数 VNodeComponentOptions 中的一个属性传入,所以我们可以通过 vnode.componentOptions.propsData 拿到 prop 数据。
接着看 updateChildComponent 函数,它的定义在 src/core/instance/lifecycle.js 中:
js
export function updateChildComponent (
vm: Component,
propsData: ?Object,
listeners: ?Object,
parentVnode: MountedComponentVNode,
renderChildren: ?Array<VNode>
) {
// ...
// update props
if (propsData && vm.$options.props) {
toggleObserving(false)
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
// keep a copy of raw propsData
vm.$options.propsData = propsData
}
// ...
}
我们重点来看更新 props 的相关逻辑;
这里的 propsData 是父组件传递的 props 数据,vm 是子组件的实例。
vm._props 指向的就是子组件的 props 值,propKeys 就是在之前 initProps 过程中,缓存的子组件中定义的所有 prop 的 key。
主要逻辑就是遍历 propKeys,然后执行 props[key] = validateProp(key, propOptions, propsData, vm) 重新验证和计算新的 prop 数据,更新 vm._props,也就是子组件的 props,这个就是子组件 props 的更新过程。
子组件重新渲染
其实子组件的重新渲染有 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,进而触发子组件的重新渲染。
以上就是当父组件 props 更新,触发子组件重新渲染的 2 种情况。
toggleObserving
最后我们在来聊一下 toggleObserving,它的定义在 src/core/observer/index.js 中:
js
export let shouldObserve: boolean = true
export function toggleObserving (value: boolean) {
shouldObserve = value
}
它在当前模块中定义了 shouldObserve 变量,用来控制在 observe 的过程中是否需要把当前值变成一个 Observer 对象。
那么为什么在 props 的初始化和更新过程中,多次执行 toggleObserving(false) 呢,接下来我们就来分析这几种情况。
在 initProps 的过程中:
js
const isRoot = !vm.$parent
// root instance props should be converted
if (!isRoot) {
toggleObserving(false)
}
for (const key in propsOptions) {
// ...
const value = validateProp(key, propsOptions, propsData, vm)
defineReactive(props, key, value)
// ...
}
toggleObserving(true)
对于非根实例的情况,我们会执行 toggleObserving(false),然后对于每一个 prop 值,去执行 defineReactive(props, key, value) 去把它变成响应式。
回顾一下 defineReactive 的定义:
js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
// ...
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ...
},
set: function reactiveSetter (newVal) {
// ...
}
})
}
通常对于值 val 会执行 observe 函数,然后遇到 val 是对象或者数组的情况会递归执行 defineReactive 把它们的子属性都变成响应式的,但是由于 shouldObserve 的值变成了 false,这个递归过程被省略了。为什么会这样呢?
因为正如我们前面分析的,对于对象的 prop 值,子组件的 prop 值始终指向父组件的 prop 值,只要父组件的 prop 值变化,就会触发子组件的重新渲染,所以这个 observe 过程是可以省略的。
最后再执行 toggleObserving(true) 恢复 shouldObserve 为 true。
在 validateProp 的过程中:
js
// check default value
if (value === undefined) {
value = getPropDefaultValue(vm, prop, key)
// since the default value is a fresh copy,
// make sure to observe it.
const prevShouldObserve = shouldObserve
toggleObserving(true)
observe(value)
toggleObserving(prevShouldObserve)
}
这种是父组件没有传递 prop 值对默认值的处理逻辑,因为这个值是一个拷贝,所以我们需要 toggleObserving(true),然后执行 observe(value) 把值变成响应式。
在 updateChildComponent 过程中:
js
// update props
if (propsData && vm.$options.props) {
toggleObserving(false)
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
// keep a copy of raw propsData
vm.$options.propsData = propsData
}
其实和 initProps 的逻辑一样,不需要对引用类型 props 递归做响应式处理,所以也需要 toggleObserving(false)。
总结
通过这一节的分析,我们了解了 props 的规范化、初始化、更新等过程的实现原理;
也了解了 Vue 内部对 props 如何做响应式的优化;
同时还了解到 props 的变化是如何触发子组件的更新。
了解这些对我们平时对 props 的应用,遇到问题时的定位追踪会有很大的帮助。