vue2.x源码中的占位符
事情的起因是我再次看了这篇掘金文章从一次 vue ssr 渲染客户端报错, 来看 ssr 客户端激活过程,里面写的在 updateClass() 中, vnode 的 tag 是 div, 而 vnode 的 elm 却是 comment. 因为 comment 节点是没有 setAttribute 方法的, 所以就报错了.让我看的不是很懂,特别是 comment 是干什么的我不是很懂,所以我搭了一个项目进行调试,理顺我不清楚的地方。
通过本篇文章,您可以学到:
- 怎么搭环境调试 vue2.x 源码?
- 占位符是什么?有什么用?
- 组件更新阶段是怎么更新占位符节点的?
环境搭建
1.我们使用 vue-cli 「搭建一个项目」:
vue create test-sourcecode
2.在项目里面「克隆 vue 源码库」:
cd test-sourcecode
git clone git@github.com:vuejs/vue.git
3.进入 vue 源码库「并使用 watch 形式编译」:
cd vue-dev
npm install
npm run dev
4.回到 test-sourcecode 文件夹,加入.eslintignore文件,「禁止 eslint 检查 vue 源码」:
cd ..
echo 'vue-dev' >> .eslintignore
5.把项目中的import Vue from 'vue'替换为import Vue from '../vue-dev/dist/vue.js'
6.使用npm run serve启动项目即可。在源码和项目上的改动都会有「热重载效果」,可以放心加console.log看输出了。
占位符是什么?有什么用?
我们经常在各种源码分析文章上面看到「占位符节点、placeholder节点」等,他们是干什么的呢?
这里我就不贴图或者代码了,直接说了。vue 的组件在生成 vnode 的过程中,对于原生节点就直接生成 tag 为对应原生 tag 的「vnode 节点」,而对于组件节点就生成 tag 是vue-component-1-App这种类型的节点,这种节点就是占位符节点,或者叫 placeholder 节点。
这种节点的作用是,「只是占据一个位置而已,并不会像原生vnode节点一样会进行更新,也不会带上对应组件vnode的数据」,它的更新流程是这样的,在比较新旧占位符节点进行更新的时候:
- 如果新旧占位符节点「有不存在」的,就直接创建或销毁占位符节点,同时通过vnode hook创建或销毁对应的组件 vnode。
- 如果新旧占位符节点「都存在,但是不是同一个节点」,就销毁旧节点,在旧占位符节点的父节点下创建新占位符节点,同时创建对应的组件 vnode。
- 如果新旧占位符节点「都存在,并且是同一个节点」,则使用vnode hook更新对应的组件 vnode。
注意:上面提到的组件 vnode 指的是,占位符对应的组件生成的组件 vnode,它才是真正控制这个组件更新的 vnode。(另外,函数式组件不会生成占位符节点,因为它没有生命周期)
那怎么「通过占位符节点找到对应的组件 vnode 呢」?其实很简单,在创建占位符节点的时候,会把组件 vnode 挂载到这个占位符节点的「componentInstance」属性上,所以这个属性就指向了组件 vnode。
组件更新节点是怎么更新占位符节点的
具体的更新流程上面已经说了,总的来说,就是使用占位符节点的 vnode hook 来更新组件 vnode ,这里详细说明一下「怎么用 vnode hook 来更新的」。
1.判断这个节点是不是占位符节点,源码中是「通过查看占位符节点有没有 hook 进行判断」的,如果是的话,就进行 prepatch:
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
2.在进行 prepatch 的时候,会「通过 componentInstance 拿到对应的组件 vnode」,然后通过 updateChildComponent 方法「更新组件 vnode 的props、listeners等」。
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
)
},
3.到这里占位符节点的「任务就已经完成」了,但是组件只更新了props、listeners等,然后组件「自身通过props、listeners这些响应式数据,来实现自身的更新」。
其它
在看源码的时候,还是有几个小问题的,留待下次解决了:
- 什么是「comment 节点」?
- 什么是「asyncPlaceholder」?
- 什么是「static vnode」?