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?