• vue3的设计思路(3)组件的本质


    虚拟 DOM 其实就是用来描述真实 DOM 的普通 JavaScript 对象,渲染器会把这个对象渲染为真实 DOM 元素。
    那么组件又是什么呢?组件和虚拟 DOM 有什么关系?渲染器如何渲染组件?接下来,我们就来讨论这些问题。

    其实虚拟 DOM 除了能够 描述真实 DOM 之外,还能够描述组件。例如使用 { tag: 'div' } 来描述

    标签

    组件并不是真实的 DOM 元素,那么如何使用虚拟 DOM 来描述呢?想要弄明白这个问题,就需要先搞清楚组件的本质是什么。一句话总结:组件就是一组 DOM 元素的封装,这组 DOM 元素就是组件要渲染的内容,因此我们可以定义一个函数来代表组件,而 函数的返回值 就代表组件要渲染的内容:

    const MyComponent = function () {
      //函数的返回值 就代表组件要渲染的内容
      return {
        tag: "div",
        props: {
          onClick: () => alert("hello"),
        },
        children: "click me",
      };
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以看到,组件的返回值也是虚拟 DOM,它代表组件要渲染的内容。
    搞清楚了组件的本质,我们就可以定义用虚拟 DOM来描述组件了。很简单,我们可以让虚拟 DOM 对象中的 tag 属性来存储组件函数:

     const vnode = {
       tag: MyComponent
     }
    
    • 1
    • 2
    • 3

    就像 tag: 'div' 用来描述

    标签一样,tag: MyComponent 用来描述组件,只不过此时的 tag 属性不是标签名称,而是组件函数。为了能够渲染组件,需要渲染器的支持。修改前面提到的 renderer 函数,如下所示:

    function renderer(vnode, container) {
      if (typeof vnode.tag === "string") {
        // 说明 vnode 描述的是标签元素
        mountElement(vnode, container);
      } else if (typeof vnode.tag === "function") {
        // 说明 vnode 描述的是组件
        mountComponent(vnode, container);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如果 vnode.tag 的类型是 字符串,说明它描述的是普通标签元素,此时调用 mountElement 函数完成渲染;
    如果 vnode.tag 的类型是函数,则说明它描述的是组件,此时调用 mountComponent 函数完成渲染。其中 mountElement 函数renderer 函数 的内容一致:

    function mountElement(vnode, container) {
      // 使用 vnode.tag 作为标签名称创建 DOM 元素
      const el = document.createElement(vnode.tag);
      // 遍历 vnode.props,将属性、事件添加到 DOM 元素
      for (const key in vnode.props) {
        if (/^on/.test(key)) {
          // 如果 key 以字符串 on 开头,说明它是事件
          el.addEventListener(
            key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
            vnode.props[key] // 事件处理函数
          );
        }
      }
      // 处理 children
      if (typeof vnode.children === "string") {
        // 如果 children 是字符串,说明它是元素的文本子节点
        el.appendChild(document.createTextNode(vnode.children));
      } else if (Array.isArray(vnode.children)) {
        // 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作  为挂载点
        vnode.children.forEach((child) => renderer(child, el));
      }
      // 将元素添加到挂载点下
      container.appendChild(el);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    再来看 mountComponent 函数是如何实现的:

    记得组件的本质:

     const vnode = {
       tag: MyComponent
     }
    
    • 1
    • 2
    • 3
    function mountComponent(vnode, container) {
      // 调用组件函数,获取组件要渲染的内容(虚拟 DOM)
      const subtree = vnode.tag();
      // 递归地调用 renderer 渲染 subtree
      renderer(subtree, container);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到,非常简单。首先调用 vnode.tag 函数,我们知道它其实就是组件函数本身,其返回值是虚拟 DOM,即组件要渲染的内容,这里我们称之为 subtree
    既然 subtree 也是虚拟 DOM,那么直接调用 renderer 函数完成渲染即可。

    大家可以在这里举一反三,比如:组件一定得是函数么?当然不是,我们完全可以使用一个 JavaScript 对象来表达组件,例如:

    // MyComponent 在这里是一个对象,而不是之前所说得的函数
    const MyComponent = {
      render() {
        return {
          tag: "div",
          props: {
            onClick: () => alert("hello"),
          },
          children: "click me",
        };
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里我们使用一个对象来代表组件,该对象有一个函数,叫作 render,其返回值代表组件要渲染的内容。

    为了完成组件的渲染,我们需要修改 renderer 渲染器以及 mountComponent 函数

    首先,修改渲染器的判断条件:

    function renderer(vnode, container) {
      if (typeof vnode.tag === "string") {
        mountElement(vnode, container);
      } else if (typeof vnode.tag === "object") {
        // 如果是对象,说明 vnode 描述的是组件
        mountComponent(vnode, container);
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    现在我们使用对象而不是函数来表达组件,因此要将 typeof vnode.tag === 'function' 修改为 typeofvnode.tag === 'object'

    接着,修改 mountComponent 函数

    function mountComponent(vnode, container) {
      // vnode.tag 是组件对象,调用它的 render 函数得到组件要渲染的内容(虚拟 DOM)
      const subtree = vnode.tag.render();
      // 递归地调用 renderer 渲染 subtree
      renderer(subtree, container);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上述代码中,vnode.tag 是表达组件的对象,调用该对象的 render 函数得到组件要渲染的内容,也就是虚拟DOM

    可以发现,我们只做了很小的修改,就能够满足用对象来表达组件的需求。那么大家可以继续发挥想象力,看看能否创造出其他的组件表达方式。其实 Vue.js 中的有状态组件就是使用对象结构来表达的。

  • 相关阅读:
    arx 实体标准
    【图神经网络论文整理】(八)—— Heterogeneous Graph Attention Network:HAN
    STM32进行LVGL裸机移植
    深度学习 神经网络(4)线性回归-Pytorch实现房价预测
    沃通CA证书支持多所高校招投标文件电子签名
    文化馆建筑方案设计原理及方案
    产业互联网周报:我国数字经济规模超45万亿元;国家网信办就个人信息出境标准公开征求意见;阿里巴巴成立子公司瓴羊;国家发改委……...
    Hadoop系列——Hadoop集群安装day2-1
    Talk | 西安交通大学博士生赵子祥:基于先验知识指导的多模态图像融合算法研究
    Worthington公司精氨酸酶:L-鸟氨酸的制备应用
  • 原文地址:https://blog.csdn.net/u013565133/article/details/127522646