当前位置:   article > 正文

vue的源码学习之六——3.patch_child.$mount(hydrating ? vnode.elm : undefined, hy

child.$mount(hydrating ? vnode.elm : undefined, hydrating)

1. 介绍

       版本:2.5.17。 

       我们使用vue-vli创建基于Runtime+Compiler的vue脚手架。

       学习文档:https://ustbhuangyi.github.io/vue-analysis/components/patch.html

 2.patch

通过上一节的分析我们知道,当我们通过 createComponent 创建了组件 VNode,接下来会走到 vm._update,执行 vm.__patch__ 去把 VNode 转换成真正的 DOM 节点。这个过程我们在前一章已经分析过了,但是针对一个普通的 VNode 节点,接下来我们来看看组件的 VNode 会有哪些不一样的地方。

patch 的过程会调用 createElm 创建元素节点,回顾一下 createElm 的实现,它的定义在 src/core/vdom/patch.js 中:

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. // ...
  11. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  12. return
  13. }
  14. // ...
  15. }

3.createComponent

我们删掉多余的代码,只保留关键的逻辑,上面的代码会判断 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值,如果为 true 则直接结束。

createComponent其实调用的就是给逐渐VNode添加的init方法

那么接下来看一下 createComponent 方法的实现:

  1. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  2. let i = vnode.data
  3. if (isDef(i)) {
  4. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  5. if (isDef(i = i.hook) && isDef(i = i.init)) {
  6. i(vnode, false /* hydrating */)
  7. }
  8. // after calling the init hook, if the vnode is a child component
  9. // it should've created a child instance and mounted it. the child
  10. // component also has set the placeholder vnode's elm.
  11. // in that case we can just return the element and be done.
  12. if (isDef(vnode.componentInstance)) {
  13. initComponent(vnode, insertedVnodeQueue)
  14. insert(parentElm, vnode.elm, refElm)
  15. if (isTrue(isReactivated)) {
  16. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  17. }
  18. return true
  19. }
  20. }
  21. }

3.1 对vnode.data 做了一些判断

  1. let i = vnode.data
  2. if (isDef(i)) {
  3. // ...
  4. if (isDef(i = i.hook) && isDef(i = i.init)) {
  5. i(vnode, false /* hydrating */)
  6. // ...
  7. }
  8. // ..
  9. }
  • vnode 是一个组件 VNode,那么条件会满足,并且得到 i 就是 init 钩子函数
  • 判断vnode.data中是否有hook,并且有init方法(因为上一节讲了会给组件VNode merage一些钩子,其中就有init,所以这里是true),就会调用init方法

3.2 init方法

init方法定义在 src/core/vdom/create-component.js 中:

其实就是和组件的data.hook钩子合并的 componentVNodeHooks 钩子对象的init方法

  1. init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
  2. if (
  3. vnode.componentInstance &&
  4. !vnode.componentInstance._isDestroyed &&
  5. vnode.data.keepAlive
  6. ) {
  7. // kept-alive components, treat as a patch
  8. const mountedNode: any = vnode // work around flow
  9. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  10. } else {
  11. const child = vnode.componentInstance = createComponentInstanceForVnode(
  12. vnode,
  13. activeInstance
  14. )
  15. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  16. }
  17. },

init 钩子函数执行也很简单,我们先不考虑 keepAlive 的情况,它是通过 createComponentInstanceForVnode 创建一个 Vue 的实例,然后调用 $mount 方法挂载子组件

3.3 createComponentInstanceForVnode 

创建一个 vnode 的实例 
src/core/vdom/create-component.js

  1. export function createComponentInstanceForVnode (
  2. vnode: any, // 组件VNode
  3. parent: any, // 当前vue实例vm
  4. ): Component {
  5. // 定义参数
  6. const options: InternalComponentOptions = {
  7. _isComponent: true,
  8. // 父VNode,是一个占位节点,Vue实例A调用B组件,B调用C组件,_parentVnode就是C组件占位符
  9. _parentVnode: vnode,
  10. //表示当前激活的子组件的父级实例,例如 app =new Vue,并调用子组件,parent就是app
  11. parent
  12. }
  13. ...
  14. // 由上一节我们知道组件会生成子构造器,vnode.componentOptions.Ctor 对应的就是子组件的构造函数
  15. return new vnode.componentOptions.Ctor(options)
  16. }

createComponentInstanceForVnode 函数构造的一个内部组件的参数,然后执行 new 
vnode.componentOptions.Ctor(options)。

上一节我们知道组件会生成子构造器,vnode.componentOptions.Ctor 对应的就是子组件的构造函数,

我们上一节分析了子构造器实际上是继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)

这里有几个关键参数要注意几个点,_isComponent 为 true 表示它是一个组件,parent 表示当前激活的组件实例。

3.4 Sub定义

sub定义在 src/core/global-api/exten.js 

  1. const Sub = function VueComponent (options) {
  2. this._init(options)cd
  3. }

所以子组件的实例化实际上就是在这个时机执行的,并且它会执行实例的 _init 方法

3.5 普通VNode 节点和组件的 VNode 初始化的不同。

他们都是在src/core/instance/init.js中初始化的,只是有一些不同,我们主要来看不同的

代码在 src/core/instance/init.js 中:

  1. Vue.prototype._init = function (options?: Object) {
  2. const vm: Component = this
  3. // merge options
  4. if (options && options._isComponent) {
  5. // optimize internal component instantiation
  6. // since dynamic options merging is pretty slow, and none of the
  7. // internal component options needs special treatment.
  8. initInternalComponent(vm, options)
  9. } else {
  10. vm.$options = mergeOptions(
  11. resolveConstructorOptions(vm.constructor),
  12. options || {},
  13. vm
  14. )
  15. }
  16. // ...
  17. if (vm.$options.el) {
  18. vm.$mount(vm.$options.el)
  19. }
  20. }

3.5.1. 合并 options 的过程有变化

合并 options 的过程有变化,_isComponent 为 true,所以走到了 initInternalComponent 过程,,而上一章普通的 VNode 节点则会走else。这个函数的实现也在当前页面 src/core/instance/init.js :

  1. export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  2. const opts = vm.$options = Object.create(vm.constructor.options)
  3. // doing this because it's faster than dynamic enumeration.
  4. const parentVnode = options._parentVnode
  5. opts.parent = options.parent
  6. opts._parentVnode = parentVnode
  7. const vnodeComponentOptions = parentVnode.componentOptions
  8. opts.propsData = vnodeComponentOptions.propsData
  9. opts._parentListeners = vnodeComponentOptions.listeners
  10. opts._renderChildren = vnodeComponentOptions.children
  11. opts._componentTag = vnodeComponentOptions.tag
  12. if (options.render) {
  13. opts.render = options.render
  14. opts.staticRenderFns = options.staticRenderFns
  15. }
  16. }

这个过程我们重点记住以下几个点即可:opts.parent = options.parentopts._parentVnode = parentVnode,它们是把之前我们通过 createComponentInstanceForVnode 函数传入的几个参数合并到内部的选项 $options 里了。

_init 函数最后执行的代码

  1. if (vm.$options.el) {
  2. vm.$mount(vm.$options.el)
  3. }

$mount 相当于执行 child.$mount(undefined, false),它最终会调用 mountComponent 方法,进而执行 vm._render() 方法

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/751966
推荐阅读
相关标签
  

闽ICP备14008679号