写点什么

【Vue2.x 源码学习】第四十一篇 - 组件部分 - 生成组件的真实节点

用户头像
Brave
关注
发布于: 4 小时前
【Vue2.x 源码学习】第四十一篇 - 组件部分 - 生成组件的真实节点

一,前言


上篇,介绍了组件部分-组件的生命周期,主要涉及以下几部分:


本篇,组件部分-生成组件的真实节点;


二,生成组件的真实节点


1,前文回顾


前篇,在 createElement 方法中,扩展了对组件的处理 createComponent:生成组件的虚拟节点;

按照模板渲染流程,接下来会进入 patch 方法,其中的 createElm 方法:将虚拟节点转化成为真实节点;


// todo 后续需详细描述相关流程,对 patch 和 createElm 方法进行必要的介绍和说明;

2,createElm 方法


在 patch 方法中,createElm 方法会将虚拟节点生成为真实节点:通过vnode.el = document.createElement(tag)直接创建出真实节点

export function createElm(vnode) {  // vnode.el:绑定真实节点与虚拟节点的映射关系,便于后续的节点更新操作
// 虚拟节点必备的三个:标签,数据,孩子 let { tag, data, children, text, vm } = vnode; if (typeof tag === 'string') { // 处理元素节点 // 创建真实节点 vnode.el = document.createElement(tag) updateProperties(vnode, data) // 处理元素的 data 属性 // 处理当前元素节点的儿子:递归创建儿子的真实节点,并添加到对应的父亲中 children.forEach(child => { // 若不存在儿子,children为空数组 vnode.el.appendChild(createElm(child)) }); } else { // 文本:文本中 tag 是 undefined vnode.el = document.createTextNode(text) // 创建文本的真实节点 } return vnode.el;}
复制代码


现在由于组件的加入,createElm 方法中可能会存在 componentOptions:


打印 createElm 查看:第一次:真实节点:id=app

第二次:组件:my-button


添加对组件的处理:

/** * 创造组件的真实节点 * @param {*} vnode  */function createComponent(vnode) {  console.log(vnode) // my-button}
export function createElm(vnode) { let { tag, data, children, text, vm } = vnode; if (typeof tag === 'string') {// 元素 or 组件 // 添加对组件的处理 if(createComponent(vnode)){ // 将组件的虚拟节点,创建成为组件的真实节点
} // 创建真实节点 vnode.el = document.createElement(tag) updateProperties(vnode, data) children.forEach(child => { vnode.el.appendChild(createElm(child)) }); } else { // 文本 vnode.el = document.createTextNode(text) } return vnode.el;}
复制代码

3,组件的初始化 Hook


之前在组件的 data 属性上,扩展出了生命周期 hook;

在 createComponent 中获取 hook,如果有 hook 说明就是组件;


拿到 hook 中的 init 方法,并使用 init 方法处理 vnode:

/** * 创造组件的真实节点 * @param {*} vnode  */function createComponent(vnode) {  console.log(vnode);  let i = vnode.data;  // 先确定有 hook;再拿到 init 方法;  if((i = i.hook)&&(i = i.init)){ // 最后 i 为 init 方法    i(vnode); // 使用 init 方法处理 vnode  }}
复制代码


备注:先将 hook 赋值给 i:看是否有 hook,如果有 hook 就是组件;再将 hook 中的 init 方法赋值给 i;最终 i 就是 hook 上的init 方法;
复制代码


使用 hook 上的 init 方法处理 vnode,在 hook 中进行中组件的初始化:

/** * 创造组件的虚拟节点 componentVnode */function createComponent(vm, tag, data, children, key, Ctor) {
if(isObject(Ctor)){ Ctor = vm.$options._base.extend(Ctor) }
data.hook = { init(){ // 创建组件的实例并挂载 let child = new Ctor({}); child.$mount(); }, prepatch(){}, postpatch(){} } let componentVnode = vnode(vm, tag, data, undefined, key, undefined, {Ctor, children, tag}); return componentVnode;}
复制代码


在 new Ctor 时,会执行 _init 进行组件的初始化:// 调用子类的初始化 _init 方法

  Vue.prototype._init = function (options) {    const vm = this;    vm.$options = mergeOptions(vm.constructor.options, options);    initState(vm);    // 由于 el 不存在,所以不会执行 vm.$mount    if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }
复制代码


通过child.$mount();进行挂在,但没有传参 el = null,所以不会挂载:

  Vue.prototype.$mount = function (el) {    const vm = this;    const opts = vm.$options;    el = document.querySelector(el); // 获取真实的元素    vm.$el = el;// $el:页面上的真实元素
if (!opts.render) { let template = opts.template; if (!template) { template = el.outerHTML; }
let render = compileToFunction(template); opts.render = render; }
mountComponent(vm); }
复制代码


如果组件的 render 函数不存在,使用组件的 template 编译为 render 函数,并保存起来,之后 mountComponent 进行组件的挂载:

export function mountComponent(vm) {
let updateComponent = ()=>{ vm._update(vm._render()); }
callHook(vm, 'beforeCreate'); // 生成渲染 watcher :每个组件都具有独立的渲染 watcher new Watcher(vm, updateComponent, ()=>{ callHook(vm, 'created'); },true) callHook(vm, 'mounted');}
复制代码


updateComponent 会调用 _render 方法根据子组件创造虚拟节点:

  Vue.prototype._render = function () {    const vm = this;    let { render } = vm.$options;    let vnode = render.call(vm);    return vnode  }
复制代码


通过 render.call 产生虚拟节点,这个 vnode 就是模板的 button


三,结尾


本篇,介绍了组件部分-生成组件的真实节点;


下篇,待定

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第四十一篇 - 组件部分 - 生成组件的真实节点