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

news2024/11/14 20:21:06

一,前言

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

  • 组件初始化情况;
  • 组件合并的位置;
  • 组件合并的策略;
  • 组件合并后测试;

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


二,前文回顾

  • 通过 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,测试组件虚拟节点生成

image.png

componentVnode 即为组件的虚拟节点;

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


四,结尾

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

  • 组件编译流程介绍:html->render->vnode
  • 创建组件虚拟节点:createComponent

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】对<进阶版三子棋>的完善和改进

这篇文章主要是对前面三子棋游戏的完善和改进。 文章目录 目录 1.将棋子*和#&#xff0c;改为1和0&#xff1b; 2.电脑下棋显示坐标 3.可以选择电脑先手或玩家先手 4.在退出游戏时显示游戏信息 5.完善后的游戏效果 二、完整程序代码 1.game.h 2.test.c 3.game.c 总结 前言 h…

【教程】虚拟环境与Pytorch安装

【教程】虚拟环境与Pytorch安装NVIDIA驱动安装虚拟环境创建激活/删除相关库的安装Pytorch安装安装地址可能遇到的问题处理报错安装卡顿测试是否安装完成参考NVIDIA驱动安装 NVIDIA驱动可在官网进行安装&#xff1a;NVIDIA驱动官网 命令行输入nvidia-smi可查看cuda版本等信息&…

Linux常用命令——ss命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) ss 比 netstat 好用的socket统计信息&#xff0c;iproute2 包附带的另一个工具&#xff0c;允许你查询 socket 的有关统计信息。 补充说明 ss命令用来显示处于活动状态的套接字信息。ss命令可以用来获取socket…

XGBoost的原理、工程实现与优缺点

Xgboost简介 XGBoost是陈天奇等人开发的一个开源机器学习项目&#xff0c;高效地实现了GBDT算法并进行了算法和工程上的许多改进&#xff0c;被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。XGBoost本质上还是一个GBDT&#xff0c;但是力争把速度和效率发…

C++普通类,派生类,虚基类的成员构造顺序以及构造函数调用顺序详解

目录前言普通类构造析构顺序解析依赖关系产生的错误派生类构造析构顺序解析扩展菱形多继承场景含虚基类的派生类构造析构顺序解析扩展菱形多继承场景(引入虚继承)前言 C规定“对象的析构过程必须与其构造过程相反”这一语法规则。 因此我们研究透彻了构造过程&#xff0c;那么…

宕机了,Redis如何避免数据丢失?

今天是大年初一&#xff0c;祝大家新年快乐&#xff0c;新的一年技术增进&#xff0c;工资翻倍。 目前&#xff0c;Redis的持久化主要有两大机制&#xff0c;即AOF日志和RDB快照&#xff0c;在接下来的两节课里&#xff0c;我们就分别学习一下吧。 AOF日志是如何实现的&#…

结构型模式-装饰器模式

1.概述 快餐店有炒面、炒饭这些快餐&#xff0c;可以额外附加鸡蛋、火腿、培根这些配菜&#xff0c;当然加配菜需要额外加钱&#xff0c;每个配菜的价钱通常不太一样&#xff0c;那么计算总价就会显得比较麻烦。 使用继承的方式存在的问题&#xff1a; 扩展性不好 如果要再加…

Alibaba微服务组件Sentinel学习笔记

1 .Sentinel 是什么 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件&#xff0c;主要以 流量为切入点&#xff0c;从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的…

一篇读懂图神经网络

来源&#xff1a;投稿 作者&#xff1a;张宇 编辑&#xff1a;学姐 近年来&#xff0c;作为一项新兴的图数据学习技术&#xff0c;图神经网络&#xff08;GNN&#xff09;受到了非常广泛的关注&#xff0c;在各大顶级学术会议上&#xff0c;图神经网络相关的论文也占了相当可观…

https://app.diagrams.net/在线画图的一些技巧

最近工作需要,实践了在线画图的case, 下面就把使用心得记录一下: 关于diagrams 的一些小技巧: 登入的网页是:Flowchart Maker & Online Diagram Software 1: 利用group 的选项,这个可以整体移动,不用担心会漏掉一个: 就是选中一个图标,然后,看右边arrange 下面…

20230123使AIO-3568J开发板在Android12下永不休眠

20230123使AIO-3568J开发板在Android12下永不休眠 2023/1/23 13:59 1、 Z:\android12-rk3568-new\device\rockchip\common\device.mk # Bluetooth HAL PRODUCT_PACKAGES \ libbt-vendor \ android.hardware.bluetooth1.0-impl \ android.hardware.bluetooth1.0-se…

Hadoop基础之《(1)—大数据基本概念》

一、Hadoop 1、Hadoop大数据框架&#xff0c;处理分布式环境下数据存储和计算 2、Hadoop的HDFS处理存储 3、Hadoop的MapReduce处理计算 map让任务数据拆分到每一台去执行 reduce处理后的任务合并 4、Hive作用是在Hadoop上能够让用户来写SQL处理数据 Hive的执行引擎&#xff0c;…

深度学习TensorFlow—GPU2.4.0版环境配置,一文简单易懂详细大全,CUDA11.0、cuDNN8.0

深度学习TensorFlow—GPU2.4.0版环境配置&#xff0c;一文简单易懂详细大全&#xff0c;CUDA11.0、cuDNN8.0 前提&#xff1a;电脑拥有英伟达独立显卡!!!&#xff0c;并且安装了anaconda&#xff01;&#xff01;&#xff01; 前提&#xff1a;电脑拥有英伟达独立显卡!!!&…

vue事件车之兄弟组件之间传值

目录前言一&#xff0c;全局事件总线介绍1.1 原理介绍1.2 x需要满足的条件二&#xff0c;知识点的复习2.1 vc是什么2.2 vm管理vc如何体现2.3 原型2.4 上述知识的串联三&#xff0c;实现需求3.1 x的编写及讲解3.2 使用x四&#xff0c;标准写法4.1 写法改动4.2 销毁五 关键代码后…

兔年首文迎新春-Cesium橘子洲烟花礼赞

兔年新春今天是兔年大年初二&#xff0c;神州大地&#xff0c;在经历了疫情的三年后迎来开放的一个春节。大家都沉浸在欢乐幸福的春节气氛中。玉兔迎新春&#xff0c;祝福齐送到&#xff1a;白兔祝你身体安康&#xff0c;黑兔祝你薪水高涨&#xff0c;灰兔送你梦想如意&#xf…

Maven高级

Maven高级 1&#xff0c;分模块开发 1.1 分模块开发设计 (1)按照功能拆分 我们现在的项目都是在一个模块中&#xff0c;比如前面的SSM整合开发。虽然这样做功能也都实现了&#xff0c;但是也存在了一些问题&#xff0c;我们拿银行的项目为例来聊聊这个事。 网络没有那么发…

Java多线程03——等待唤醒机制(and阻塞队列实现)

目录1.等待唤醒机制1.ThreadDemo2.Desk3.Cook4.Foodie2.等待唤醒机制&#xff08;阻塞队列方式实现&#xff09;1.ThreadDemo022.Cook023.Foodie023.线程的状态1.等待唤醒机制 生产者和消费者 桌子上有食物&#xff0c;消费者吃&#xff0c;桌子上没有食物&#xff0c;消费者等…

ElasticSearch 索引模板 组件模板 组合模板详细使用介绍

索引模板_template 文章目录索引模板_templateTemplate 介绍索引模板Index Template参数说明创建一个索引模板 Index Template测试不存在的索引直接添加数据创建索引总结组合索引模板 Index Template 7.8版本之后引入创建基于组件模板的索引模板 Index Template创建组件模板模拟…

LeetCode103_ 103. 二叉树的锯齿形层序遍历

LeetCode103_ 103. 二叉树的锯齿形层序遍历 一、描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&…

【头歌】顺序表的基本操作

第1关&#xff1a;顺序表的插入操作任务描述本关任务&#xff1a;编写顺序表的初始化、插入、遍历三个基本操作函数。相关知识顺序表的存储结构顺序表的存储结构可以借助于高级程序设计语言中的数组来表示&#xff0c;一维数组的下标与元素在线性表中的序号相对应。线性表的顺序…