写点什么

【Vue2.x 源码学习】第三十九篇 - 组件部分 - 创建组件虚拟节点

用户头像
Brave
关注
发布于: 17 小时前
【Vue2.x 源码学习】第三十九篇 - 组件部分 - 创建组件虚拟节点

一,前言


上篇,介绍了组件部分-组件的合并,主要涉及以下几个点:


  • 组件初始化情况;

  • 组件合并的位置;

  • 组件合并的策略;

  • 组件合并后测试;


本篇,组件部分-组件的编译;


二,前文回顾


  • 通过 Vue.component 全局 API 声明全局组件;

  • Vue.component 内部使用 Vue.extend 生成组件的构造函数;

  • 将组件构造函数维护到全局对象Vue.options.components中备用;

  • 在 new Vue 初始化时,在 mergeOptions 方法中,使用策略模式找到组件的合并策略,完成全局组件和局部组件的合并操作;


至此,在 vm.$options 中,就已经完成了组件关系的构建;

组件查找规则:优先查找局部组件,找不到继续在链上查找全局组件;


回顾模板解析流程:

  • 第一步,将 html 模板生成 AST 语法树;

  • 第二步,根据 AST 语法树生成 render 函数;

  • 第三步,在 render 函数中,调用 _c(即 createElement 方法)处理标签,生成元素标签的虚拟节点 vnode;


三,组件的编译流程

<div id="app">
<my-button></my-button>
</div>
复制代码


以上边组件为例,组件的编译过程与模板相似:

- 第一步,根据组件 html 模板生成 AST 语法树;

- 第二步,根据 AST 语法树生成 render 函数;

- 第三步,在 render 函数中,调用 _c 处理组件,生成组件的虚拟节点 componentVnode;


组件与标签编译的区别:

- 标签需要生成标签的虚拟节点;

- 组件需要生成组件的虚拟节点;


因此,可对原 createElement 方法进行扩展,使之支持组件的编译,生成组件的虚拟节点;


四,生成组件虚拟节点

1,扩展 createElement 方法


原 createElement 方法:生成标签元素的虚拟节点 vnode:

// 参数:_c('标签', {属性}, ...儿子)export function createElement(vm, tag, data={}, ...children) {  // 返回元素的虚拟节点(元素是没有文本的)  return vnode(vm, tag, data, children, data.key, undefined);}
复制代码


现在,由于组件的加入,createElement 方法中 tag 不一定是元素,还可能是组件;

export function createElement(vm, tag, data={}, ...children) {
// 添加 tag 为组件时的处理,创建出组件的虚拟节点 componentVnode // todo ... console.log(tag); return vnode(vm, tag, data, children, data.key, undefined);}
复制代码


log 打印输出 2 次:

  • 第一次:my-button(组件)

  • 第二次:div(标签)


因此,需要对 createElement 方法进行扩展:添加 tag 为组件时的处理逻辑,创建对应组件的虚拟节点componentVnode;

2,区分组件 or 元素


判断依据:tag 是否属于原始/普通标签:

  • 如果 tag 属于原始标签,说明 tag 是元素,如:div;

  • 如果 tag 不属于原始标签,说明 tag 是组件, 如:my-button;


// 添加 tag 为组件时的处理逻辑,创建出组件的虚拟节点export function createElement(vm, tag, data={}, ...children) {  // 判断是组件还是元素节点:是否属于普通标签  if (!isReservedTag(tag)) {// 组件:非普通标签即为组件    // todo...  }    // 创建元素的虚拟节点  return createComponent(vm, tag, data, children, data.key, Ctor);// 创造组件的虚拟节点}
/** * 创造组件的虚拟节点 componentVnode */function createComponent(vm, tag, data, children, key, Ctor) { // todo...}
// 判定包含关系function makeMap(str) { let tagList = str.split(','); return function (tagName) { return tagList.includes(tagName); }}
// 原始标签export const isReservedTag = makeMap( 'template,script,style,element,content,slot,link,meta,svg,view,button,' + 'a,div,img,image,text,span,input,switch,textarea,spinner,select,' + 'slider,slider-neighbor,indicator,canvas,' + 'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' + 'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown')
复制代码

3,createComponent 方法定义


获取对应组件的构造函数,并创建组件虚拟节点:

  • 通过 vm.$options.components 获取对应组件的构造函数;

  • 通过 createComponent 方法创建组件的虚拟节点;


// 添加 tag 为组件时的处理逻辑,创建出组件的虚拟节点export function createElement(vm, tag, data={}, ...children) {  if (!isReservedTag(tag)) {// 组件    // 获取组件的构造函数:之前已经保存到了全局 vm.$options.components 上;    let Ctor = vm.$options.components[tag];    // 创建组件的虚拟节点    return createComponent(vm, tag, data, children, data.key, Ctor);  }    // 创建元素的虚拟节点  return vnode(vm, tag, data, children, data.key, Ctor);}
/** * 创造组件的虚拟节点 componentVnode */function createComponent(vm, tag, data, children, key, Ctor) { let componentVnode; // todo... return componentVnode;}
复制代码


注意 Ctor 的取值:

  • 会先到vm.$options.components对象上查找局部组件,如果找到了 Ctor 会是一个对象;(因为局部组件定义不会被Vue.extend处理成为组件构造函数)

  • 如果没找到,会继续到链上找全局组件,此时的Ctor会是一个函数;(因为全局组件内部会调用Vue.extend处理成为组件构造函数)


所以,在createComponent中,当Ctor为对象时,需要先通过Vue.extend处理为组件的构造函数;

4,createComponent 方法实现


扩展 vnode 结构

首先,需要扩展 vnode 结构,添加组件选项 componentOptions:

/** * 创造组件的虚拟节点 componentVnode */function createComponent(vm, tag, data, children, key, Ctor) {  if(isObject(Ctor)){    //  todo:获取 Vue.extend,并将对象处理成为组件的构造函数  }    // 创建 vnode 时,组件是没有文本的,需要传入 undefined  let componentVnode = vnode(vm, tag, data, children, key, undefined, Ctor);  return componentVnode;}
// options:可能是组件的构造函数,也可能是对象function vnode(vm, tag, data, children, key, text, options) { return { vm, // 谁的实例 tag, // 标签 data, // 数据 children, // 儿子 key, // 标识 text, // 文本 componentOptions: options // 组件的选项,包含 Ctor 及其他扩展项 }}
复制代码


如何获取到 Vue.extend

  • 为了便于后续使用 Vue.extend,在初始化时,将 Vue 保存到 Vue.options._base;

// src/global-api/index.js
export function initGlobalAPI(Vue) { Vue.options = {}; // 当组件初始化时,会使用 Vue.options 和组件 options 进行合并; // 在这个过程中,_base 也会被合并到组件的 options 上; // 之后所有的 vm.$options 就都可以取到 _base 即 Vue; // 这样,在任何地方访问 vm.$options._base 都可以拿到 Vue; Vue.options._base = Vue; Vue.options.components = {}; Vue.extend = function (opt) { } Vue.component = function (id, definition) { }}
复制代码


  • 当组件初始化时,会将 Vue.options 和组件的 options 进行合并,在这个过程中 _base 也将被合并到组件的 options 上;

  Vue.prototype._init = function (options) {    const vm = this;    // 使用 Vue 的 options 和组件自己的options进行合并    vm.$options = mergeOptions(vm.constructor.options, options);    initState(vm);    if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }
复制代码


这样一来,所有的 vm.$options 就都能够取到 _base 即 Vue 了;

/** * 创造组件的虚拟节点 componentVnode */function createComponent(vm, tag, data, children, key, Ctor) {
if(isObject(Ctor)){ // 获取 Vue 并通过 Vue.extend 将对象处理成为组件的构造函数 Ctor = vm.$options._base.extend(Ctor) }
// 创建vnode时,组件是没有文本的,需要传入 undefined let componentVnode = vnode(vm, tag, data, children, key, undefined, Ctor); return componentVnode;}
复制代码


所以,所有的组件都要通过 Vue.extend 方法,生成组件的构造函数:

  • 全局组件:在 Vue.component 内部就被 Vue.extend 处理;

  • 局部组件:在 createComponent 创建组件虚拟节点时,被 Vue.extend 处理;


扩展组件选项 componentOptions

  • 组件没有孩子,“组件的孩子”就是插槽,所以 children 应放入componentOptions组件选项中;

  • 当是组件是,data 数据也属于组件,同样也需要放入componentOptions组件选项中...

  • 完整的 componentOptions 应包括:Ctor、propsData、listeners、tag、children;


function createComponent(vm, tag, data, children, key, Ctor) {  if(isObject(Ctor)){    Ctor = vm.$options._base.extend(Ctor)  }  // 注意:组件没有孩子,组件的孩子就是插槽,将 children 放到组件的选项中  let componentVnode = vnode(vm, tag, data, undefined, key, undefined, {Ctor, children, tag});  return componentVnode;}
复制代码


备注:组件虚拟节点的唯一标识应为:vue-component-cid-name- cid:组件实例的唯一标识;- name:组件定义中的 name 属性;
复制代码

5,测试组件虚拟节点生成



componentVnode 即为组件的虚拟节点;

其中,componentOptions 选项中包含组件的构造函数 Ctor;


四,结尾


本篇,介绍了组件部分-组件的编译,主要涉及以下几部分:


  • 组件编译流程介绍:html->render->vnode

  • 创建组件虚拟节点:createComponent


下一篇,组件部分-组件的生命周期;

用户头像

Brave

关注

还未添加个人签名 2018.12.13 加入

还未添加个人简介

评论

发布
暂无评论
【Vue2.x 源码学习】第三十九篇 - 组件部分 - 创建组件虚拟节点