1、什么是vnode hook?
在对新旧vnode
进行patch
的过程中,在不同的阶段提供的一些访问vnode
的钩子。vnode hook
贯穿整个patch
的过程, 像dom元素的属性、事件、class、style的更新,ref, 指令,transition都是通过vnode hook
来实现的。patch
方法是处理diff
的整体流程逻辑的,而hook
就是用来干活的。
2、组件vnode的hook。
- init:创建内部组件时调用
在init内部会根据vnode创建组件实例:
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
// 如果是使用了keepAlive的组件,则会复用上一次的组件实例
const mountedNode: any = vnode // work around flow
// 并且进行prepatch
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 创建组件实例
// 在组件的vnode init的时候会创建对应的组件实例
// init执行完也代表组件已经创建成功,dom已经在内存中创建出来了
// 真正执行mounted钩子是在根节点的dom元素生成后, 通过insertedVnodeQueue来批量执行的
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode, // VNode节点对象
activeInstance // 父组件实例
)
// 挂载组件
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
- insert: 当整个vnode树都patch完成才会执行,通过维持
insertedVnodeQueue
来保证所有组件的mouted
顺序执行。
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
// 只有根组件的mounted事件是在自己patch完成后执行的
// 内部组件不是这样的,虽然内部组件patch完成后dom元素已经生成了并且插入到了父节点中
// 但是mounted生命周期是延迟执行的。
if (isTrue(initial) && isDef(vnode.parent)) {
// 将当前vnode的插入队列放到父组件的pendingInsert上
vnode.parent.data.pendingInsert = queue
} else {
// 遍历vnode队列,执行vonde.data.hook.insert
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
执行mounted
。
insert (vnode: MountedComponentVNode) {
const { context, componentInstance } = vnode
// 在子组件调用$mount成功后并不会触发 mounted 钩子, 只有根组件才会调用
// 子组件的mounted是在这里触发的
// 这样可以保证在任何一个子组件的mounted里通过refs访问任何组件的实例都能有结果
// 假设是在$mount里调用mounted,此时子组件的下一个兄弟组件还未创建成功
// 这样的话是访问不到下一个兄弟组件的
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
if (vnode.data.keepAlive) {
if (context._isMounted) {
// vue-router#1212
// During updates, a kept-alive component's child components may
// change, so directly walking the tree here may call activated hooks
// on incorrect children. Instead we push them into a queue which will
// be processed after the whole patch process ended.
queueActivatedComponent(componentInstance)
} else {
activateChildComponent(componentInstance, true /* direct */)
}
}
},
- prepatch:对组件patch之前调用
function patchVnode() {
// ....
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
// ....
}
prepatch的逻辑:
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
// 当vnode进行patch时会对上一次创建出来的组件实例进行复用
// 然后对组件的属性、事件、子节点进行更新
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
debugger
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
- destroy:vnode被销毁时调用
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
// 先调用用户定义的destroy hook, 再调用vue定义的destroy hook
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
// 如果存在children, 则递归调用
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
destroy的逻辑:
destroy (vnode: MountedComponentVNode) {
// 当vnode节点销毁时对应的组件实例也会被销毁
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
// 如果不是keep-alive组件, 直接销毁组件
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
// keep-alive组件
deactivateChildComponent(componentInstance, true /* direct */)
}
}
}
3、普通的vnode的hook
- create:vnode对应的dom元素创建完成时调用。
- updateAttrs:更新dom元素的普通属性
- updateClass:更新dom元素的class
- updateDOMListeners:更新dom元素的事件
- updateDOMProps:更新dom元素的innerHTML、textContent等特殊属性。
- updateStyle:更新dom元素的style
- _enter:transition组件使用,动画逻辑是在这里定义的
- create:registerRef注册ref
- updateDirectives:更新指令
- update
- updateAttrs(oldVnode, vnode)
- updateClass(oldVnode, vnode)
- updateDOMListeners(oldVnode, vnode)
- updateDOMProps(oldVnode, vnode)
- updateStyle(oldVnode, vnode)
- update(oldVnode, vnode)
- updateDirectives(oldVnode, vnode)
- remove: dom元素从页面中移除前调用,transition会使用到。
- destroy
- destroy(vnode): 销毁ref
- unbindDirectives(vnode):指令unbind
- active: keep-alive组件重新展示时调用,transition会使用到。
4、vnode hook 测试代码
有兴趣的朋友可以跑一下下面的代码,可能会对patch的过程理解有帮助。我这篇文章可能是从不同的角度去看patch
,已经有点钻牛角尖了, 如果对vue的pacth
过程感兴趣的朋友, 刚开始千万不要迷失在这些hook里面。知道大概是做什么的就好, 还是得从整体流程去理解。比如指令、ref、dom的等建议不要和patch的整体逻辑一起研究,单独拿出来研究即可。
const Comp = {
data() {
return {
msg: 1,
};
},
props: {
msg1: String,
},
methods: {
handleClick() {
this.msg = Math.random();
},
},
render(h) {
let vm = this;
return h(
"div",
{
hook: {
// 创建的时候调用的hook
create(oldVnode, vnode) {
// 这个时候已经根据vnode创建出dom节点了, 但是还未插入到父节点中去
console.log("create", oldVnode, vnode);
vnode.elm.style.cssText = "color: red";
},
insert(oldVnode, vnode) {
console.log("insert", oldVnode, vnode);
},
// 更新时调用的 hook
prepatch(oldVnode, vnode) {
console.log("prepatch", oldVnode, vnode);
},
update(oldVnode, vnode) {
console.log("update", oldVnode, vnode);
},
postpatch(oldVnode, vnode) {
console.log("postpatch", oldVnode, vnode);
},
destroy(oldVnode, vnode) {
console.log("destroy", oldVnode, vnode);
},
},
on: {
click: this.handleClick,
},
},
[
h(
"button",
{
on: {
click: this.handleClick,
},
},
"组件内部修改状态"
),
this.msg + " " + this.msg1,
]
);
},
};
new Vue({
el: "#app",
components: { Comp },
data() {
return {
showComp: true,
msg1: "a",
};
},
methods: {
handleToggle() {
this.showComp = !this.showComp;
},
changeMsg1() {
this.msg1 = Math.random() + "";
},
},
render(h) {
return h("div", [
h(
"button",
{
on: {
click: this.handleToggle,
},
},
this.showComp ? "隐藏" : "显示"
),
h(
"button",
{
on: {
click: this.changeMsg1,
},
},
"给子组件发送消息"
),
this.showComp
? h("Comp", {
props: {
msg1: this.msg1,
},
hook: {
init(vnode, hydrating) {
console.log("组件Comp init", vnode, hydrating);
},
insert(vnode) {
console.log("组件Comp insert", vnode);
},
prepatch(oldVnode, vnode) {
console.log("组件Comp prepatch", oldVnode, vnode);
},
postpatch(oldVnode, vnode) {
console.log("组件Comp postpatch", oldVnode, vnode);
},
destroy(vnode) {
console.log("组件Comp destroy", vnode);
},
},
})
: null,
]);
},
});
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!