Fork me on GitHub

vue2.x源码中的占位符

vue2.x源码中的占位符

事情的起因是我再次看了这篇掘金文章从一次 vue ssr 渲染客户端报错, 来看 ssr 客户端激活过程,里面写的在 updateClass() 中, vnode 的 tag 是 div, 而 vnode 的 elm 却是 comment. 因为 comment 节点是没有 setAttribute 方法的, 所以就报错了.让我看的不是很懂,特别是 comment 是干什么的我不是很懂,所以我搭了一个项目进行调试,理顺我不清楚的地方。

通过本篇文章,您可以学到:

  1. 怎么搭环境调试 vue2.x 源码?
  2. 占位符是什么?有什么用?
  3. 组件更新阶段是怎么更新占位符节点的?

环境搭建

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的数据,它的更新流程是这样的,在比较新旧占位符节点进行更新的时候:

  1. 如果新旧占位符节点有不存在的,就直接创建或销毁占位符节点,同时通过创建或通过vnode hook销毁对应的组件 vnode。
  2. 如果新旧占位符节点都存在,但是不是同一个节点,就销毁旧节点,在就占位符节点的父节点下创建新占位符节点,同时创建对应的组件 vnode。
  3. 如果新旧占位符节点都存在,并且是同一个节点,则使用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这些响应式数据,来实现自身的更新

其它

在看源码的时候,还是有几个小问题的,留待下次解决了:

  1. 什么是comment 节点
  2. 什么是asyncPlaceholder?
  3. 什么是static vnode?
posted @ 2020-11-08 17:08  馒头加梨子  阅读(940)  评论(0编辑  收藏  举报