组件渲染源码分析
组件渲染主要分为分为两步, 一是创建组件对应的虚拟dom 二是将组件所生成的真实dom插入到父组件的组件标签所对应的位置
-
- 创建组件对应的虚拟dom
-
- vue内部会先生成一个render函数, 执行render函数就会生成虚拟dom, 所以创建组件虚拟dom这一步发生在render函数里面的_c
-
- render函数, 实际上是通过ast转换成特定格式的字符串, 再将字符串插入到new Function() { with(this); 字符串 }这种方式来实现生成一个render函数的(具体实现过程可以去看我关于vue源码解读的文章), render函数内部通过调用_c 转换成虚拟dom的
-
- _c方法接收一个对象, 对象里面包含tag data childern这些属性, tag就是当前标签的名称(比如div就是tag的值, my-component这个也是tag的值), data就是定义在组件标签身上的属性(父传给子的属性, 子发出的方法都在这个data里面), childern就是当前标签内部的子标签对象
-
- _c方法执行时, 会拿到这个tag值, 判断这个tag值是不是html定义的那些标签, 如果是就表示是一个普通标签, 就去调用生成普通虚拟dom的方法, 如果不是,就证明这是一个组件标签, 那就进入生成组件虚拟dom的方法
-
- 生成组件虚拟dom方法, 这个方法内部会先创建一个对象, 这个对象就是虚拟dom, 接着向对象身上增加data属性, data是一个对象, data中有一个hooks属性, 这个hooks属性是一个对象, 里面包含两个方法, 一个是init方法, 另一个是prepatch方法(prepatch方法主要在组件props变化的时候调用, 后续有时间的话, 会再写一篇组件更新对比的文章), init方法主要就是用来进行组件初始化操作的
-
- init方法内部会拿到组件所对应的Ctor构造函数, 接着创建组件实例, 执行mount方法(这块不需要先懂为什么这样做,等到组件虚拟dom转真实dom时,我会再重新对这块再说一下,现在只需要有个印象就行),做三件事,1获取到组件构造函数2创建组件实例3执行组件实例的mount方法(这块不需要先懂为什么这样做, 等到组件虚拟dom转真实dom时, 我会再重新对这块再说一下, 现在只需要有个印象就行), 做三件事, 1获取到组件构造函数 2创建组件实例 3执行组件实例的mount方法(这块不需要先懂为什么这样做,等到组件虚拟dom转真实dom时,我会再重新对这块再说一下,现在只需要有个印象就行),做三件事,1获取到组件构造函数2创建组件实例3执行组件实例的mount方法(注意: $mount() 什么都没有传入, 这块很重要)
-
- init方法添加完之后, 就会执行Vue.extend方法, Vue.extend方法需要传入当前的组件所对应的配置对象, 也就是new Vue中传入的对象的components的my-component属性所对应的value, Vue.extend方法拿到配置对象之后, 会将全局Vue.options和配置对象进行合并, 合并之后会作为Vue.extend返回的构造函数Sub.options的值(这块不懂的看我关于Vue.extend源码分析的文章), 所以这块执行Vue.extend返回一个构造函数, 将这个构造函数添加到组件的虚拟dom对象身上, 属性名为Ctor
-
- 这里还会将参数data(这里data指的是调用生成组件虚拟dom方法传过来的data, 也就是组件标签身上的属性)进行剥离,
如果在组件的配置对象身上的props定义的有data中的属性, 就将data中的这个属性取出来添加到props变量身上, 还有事件也会处理(这里不一一说, 这篇主要是理解组件渲染流程), 剥离完之后, data就只剩下没有定义的在props身上的了, data就会作为组件虚拟dom的attrs属性(这个也就是组件内部$attrs的一个由来), props作为组件虚拟dom的props属性值
- 这里还会将参数data(这里data指的是调用生成组件虚拟dom方法传过来的data, 也就是组件标签身上的属性)进行剥离,
-
- 组件虚拟dom创建完毕, 重点两件事情 1创建出组件构造函数 2向组件虚拟dom对象身上增加init钩子函数
-
- 将组件所生成的真实dom插入到父组件的组件标签所对应的位置
这个过程会比较长, 比较绕, 我先大概解释一下, vue解析到组件所对应的虚拟dom时, 就会去调用组件的init方法, 组件init方法内部会创建组件实例, 组件构造函数中的_init方法执行, _init方法执行完成之后, 实例执行$mount方法, $mount方法中因为没有传参数会将组件的虚拟dom(也就是将组件template转换成的虚拟dom)转换成真实的dom, 真实dom作为组件的el属性, 之后组件方法执行完毕, 返回生成组件标签真实节点的地方, 判断组件实例身上有el属性的话, 就会将组件的el属性作为当前组件标签所对应的真实dom, 插入到vue实例生成的真实dom里面, 真实dom挂载到页面上, 组件渲染完成
-
- 虚拟dom全部创建完成之后, 就会将虚拟dom转换成真实dom, 虚拟dom转真实dom这一步发生在_update方法中
-
- _update方法内部会判断是否有上一次生成的虚拟dom(通过prevnode值判断, 具体名称不记得了), 如果没有就证明这是第一次渲染就进入createElement方法中执行
-
- createElement方法, 拿到虚拟dom, 生成最外层的真实dom, 之后遍历虚拟dom的childern每一项, 继续调用createElement, 生成之后的真实元素, 插入到上一级的真实dom里面(这里指的就是最外层了), createElement执行, 遇到了组件虚拟dom
-
- createElement方法遇到组件虚拟dom, 会判断当前虚拟dom身上是否有data属性, data中是否有hooks, hooks中是否有init方法, 有就去执行init方法(注意这里会跳到组件虚拟dom的init方法中, 会在init中执行很多方法, 一会执行完都要回到这里), init方法执行之后, 回到这里, 继续执行看当前组件虚拟dom所对应的组件实例身上是否有el属性, 如果有就将el作为当前组件标签的真实节点, 插入到上一级的真实dom里面
-
- init方法, 现在回到了的组件虚拟dom的init方法中, 这里就会拿到组件的构造函数, 然后创建实例, 将这个实例添加到组件虚拟dom对象身上, 实例创建就会执行构造函数的_init方法, 构造函数的_init方法中会传入一个对象, 对象中有_isComponent: true, 组件虚拟dom身上的props attrs都会放在这个对象身上,
-
- 执行_init方法, _init方法会判断_isComponent如果为true, 就会vm.options = Object.create(vm.constructor.options), 也就是说会创建一个对象, 这个对象的原型指向其构造函数的options(构造函数的options会将全局Vue.options与当前配置对象进行合并), 这个对象身上找不到属性时, 就会去原型身上查找(这里有面试题, 为什么data必须是一个函数, 我后续会再专门针对这个问题讲一下), _init方法执行完毕
-
- 执行$mount方法, 注意这里什么参数都没有传进去, mount方法主要就是将template转换成render函数,创建一个watcher执行vm.update(vm.render())(不了解的话,跳过我的这句话,就认为mount方法主要就是将template转换成render函数, 创建一个watcher执行vm._update(vm.render())(不了解的话, 跳过我的这句话, 就认为mount方法主要就是将template转换成render函数,创建一个watcher执行vm.update(vm.render())(不了解的话,跳过我的这句话,就认为mount会将虚拟dom转成真实dom就好了),
组件虚拟dom生成之后, 执行组件的_update方法
- 执行$mount方法, 注意这里什么参数都没有传进去, mount方法主要就是将template转换成render函数,创建一个watcher执行vm.update(vm.render())(不了解的话,跳过我的这句话,就认为mount方法主要就是将template转换成render函数, 创建一个watcher执行vm._update(vm.render())(不了解的话, 跳过我的这句话, 就认为mount方法主要就是将template转换成render函数,创建一个watcher执行vm.update(vm.render())(不了解的话,跳过我的这句话,就认为mount会将虚拟dom转成真实dom就好了),
-
- 组件的_update方法执行, _update根据prevnode判断是不是第一次渲染, 是第一次渲染就去执行组件的createElement方法
-
- 组件的createElement方法执行, 会接收$mount传过来的值, 如果接收过来的是undefined就是没有传的, vue就会将组件的虚拟dom转成真实dom, 然后放到组件的el属性身上
-
- 组件init方法执行完成, 会到init构造函数调用那里, 拿到组件的el属性之后, 就将其作为组件标签所对应的真实dom, 再将这个真实dom插入到父级真实dom里面
到此为止, 组件渲染流程完毕(上述有些地方可能与源码中并不一一吻合, 但上述就是vue渲染组件的一个整体源码流程)
- 组件init方法执行完成, 会到init构造函数调用那里, 拿到组件的el属性之后, 就将其作为组件标签所对应的真实dom, 再将这个真实dom插入到父级真实dom里面
- 将组件所生成的真实dom插入到父组件的组件标签所对应的位置
398

被折叠的 条评论
为什么被折叠?



