prop是父子组件数据单向传递的桥梁,那么,父组件中数据变化后子组件中的视图是如何渲染的呢?
const childCom = {
props: {
childNum: {
type: Number,
default: function () {
return 1
}
}
},
template: `<div>{{childNum}}</div>`
}
const parentCom = {
components: {
childCom
},
data() {
return {
parentData: 100,
}
},
template: `<div @click="changeData">
<childCom :childNum = 'parentData'></childCom>
</div>`,
methods: {
changeData() {
this.parentData = 1000;
}
},
}
new Vue({
el: "#app",
render(h) {
return h(parentCom);
},
});
vue大体渲染逻辑是先通过this.init执行初始化操作,最后通过vm._update(vm._render(), hydrating)来执行渲染的逻辑,整个渲染过程又可以分为简单的模板渲染和复杂的组件渲染。
当前例子中,通过循环的方式从父级开始进行流程的执行。
1、render函数
这里通过compileToFunctions函数将模板<childCom @click.native="changeData" :childNum = 'parentData'></childCom>转换成:
function anonymous() {
with (this) {
return _c("childCom", {
attrs: { childNum: parentData },
nativeOn: {
click: function ($event) {
return;
changeData($event);
},
},
});
}
}
这里的属性attrs: { childNum: parentData }其中的parentData在with(this)作用域下就是this.parentData,即attrs: { childNum: 100 }。
2、createComponent函数
在createComponent创建组件vNode的过程中,有var propsData = extractPropsFromVNodeData(data, Ctor, tag):
function extractPropsFromVNodeData (
data,
Ctor,
tag
) {
// we are only extracting raw values here.
// validation and default values are handled in the child
// component itself.
var propOptions = Ctor.options.props;
if (isUndef(propOptions)) {
return
}
var res = {};
var attrs = data.attrs;
var props = data.props;
if (isDef(attrs) || isDef(props)) {
for (var key in propOptions) {
var altKey = hyphenate(key);
if (process.env.NODE_ENV !== 'production') {
var keyInLowerCase = key.toLowerCase();
if (
key !== keyInLowerCase &&
attrs && hasOwn(attrs, keyInLowerCase)
) {
tip(
"Prop \"" + keyInLowerCase + "\" is passed to component " +
(formatComponentName(tag || Ctor)) + ", but the declared prop name is" +
" \"" + key + "\". " +
"Note that HTML attributes are case-insensitive and camelCased " +
"props need to use their kebab-case equivalents when using in-DOM " +
"templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"."
);
}
}
checkProp(res, props, key, altKey, true) ||
checkProp(res, attrs, key, altKey, false);
}
}
return res
}
function checkProp (
res: Object,
hash: ?Object,
key: string,
altKey: string,
preserve: boolean
): boolean {
if (isDef(hash)) {
if (hasOwn(hash, key)) {
res[key] = hash[key]
if (!preserve) {
delete hash[key]
}
return true
} else if (hasOwn(hash, altKey)) {
res[key] = hash[altKey]
if (!preserve) {
delete hash[altKey]
}
return true
}
}
return false
}
这里通过var propOptions = Ctor.options.props获取到propOptions为childNum: { type: Number, default: function () { return 1 } },执行checkProp(res, props, key, altKey, true)返回false,继续执行到 checkProp(res, attrs, key, altKey, false)的方式,将属性attrs: { childNum: 100 }和key作为参数传入执行获取到{childNum: 100}。最后将propsData做为传入创建vNode实例:
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
3、patch函数
在获取到vNode后会执行patch到createElm中,这里是组件vNode,进而执行到逻辑createComponent:
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
执行到i(vnode, false /* hydrating */),这里的i是钩子函数init:
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
// ...
}
实例化const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )的过程中会执行到构造函数的this._init:
4、initProps
在this._init初始化过程中执行到initState(vm):
function initState (vm) {
// ...
var 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)
}
这里对defineReactive(props, key, value)进行响应式处理,当进行vNode创建过程中会访问到props中的childNum,通过dep.depend();进行依赖收集,此时childNum锁定的发布者dep的subs就有用来渲染子组件childCom的渲染watcher。
响应式处理请移步:juejin.cn/post/713099…
5、$mount函数
当执行完const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance )后,执行child.$mount(hydrating ? vnode.elm : undefined, hydrating)进行child的挂载。最后得到child.$el为"<div>100</div>"。
组件渲染请移步:juejin.cn/post/712909…
6、数据改变时的逻辑
(1)dep.notify
当数据发生改变时,this.parentData发生变化,执行发布者dep的dep.notify进行父组件的重新渲染。
(2)patch
父组件patch的过程中会执行到patchVnode:
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
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)
}
// ...
}
执行到i(oldVnode, vnode),这里的i是钩子函数prepatch:
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
// ...
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的更新:
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[key] = validateProp(key, propOptions, propsData, vm)的过程中,对props[key]进行了计算并赋值,进而引起props[key]锁定的发布者dep的dep.notify的执行,进而执行子组件的重新渲染。
以上就是prop改变引起子组件的全过程。
小结:
prop改变的过程中主要关注render在with(this)中的prop真实值、propsData的获取、initProps过程中对prop进行响应式处理以及数据变化时prepatch的执行。