zoukankan      html  css  js  c++  java
  • React: 无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程。

    先以下面一段简单的代码举例:

    const Greeting = function ({name}) {
      return <div>{`hello ${name}`}</div>;
    };
    
    const App = <Greeting name="scott"/>;
    
    console.log(App);
    
    ReactDOM.render(App, document.getElementById('root'));
    

    可以看出,Greeting 是一个无状态组件,我们来看看编译过后的可执行代码:

    var Greeting = function Greeting(_ref) {
      var name = _ref.name;
    
      return React.createElement(
        "div",
        null,
        "hello " + name
      );
    };
    
    var App = React.createElement(Greeting, { name: "scott" });
    
    console.log(App);
    
    ReactDOM.render(App, document.getElementById('root'));
    

    我们看到,调用 Greeting 组件时传入的 name 属性,出现在 React.createElement() 方法的第二个参数中,这和前面介绍的 JSX 是一致的,不同的是,React.createElement() 方法的第一个参数不再是一个标签名,而是一个函数引用,指向了我们声明的 Greeting 组件,而 name 属性也作为参数的成员出现在组件内部,这个参数名为 _ref,实则是我们熟知的 props。

    下图是我们运行上面代码之后,打印出的 App 数据结构,即虚拟 DOM 结构:

    我们再来看一个稍微复杂些的例子:

    const Greeting = function ({name}) {
      return (
        <div>{`hello ${name}`}</div>
      );
    };
    
    const Container = function ({children}) {
      return (
        <div className="container">
          {children}
        </div>
      );
    };
    
    const App = (
      <Container>
        <Greeting name="scott"/>
        <Greeting name="jack"/>
        <Greeting name="john"/>
      </Container>
    );
    
    console.log(App);
    
    ReactDOM.render(App, document.getElementById('root'));
    

    在上面代码中,我们定义了两个无状态组件,其中 Container 用来作为外层的容器,Greeting 则用来显示实际的业务视图。

    现在再来看看编译后的代码结构:

    var Greeting = function Greeting(_ref) {
      var name = _ref.name;
    
      return React.createElement(
        "div",
        null,
        "hello " + name
      );
    };
    
    var Container = function Container(_ref2) {
      var children = _ref2.children;
    
      return React.createElement(
        "div",
        { className: "container" },
        children
      );
    };
    
    var App = React.createElement(
      Container,
      null,
      React.createElement(Greeting, { name: "scott" }),
      React.createElement(Greeting, { name: "jack" }),
      React.createElement(Greeting, { name: "john" })
    );
    
    console.log(App);
    
    ReactDOM.render(App, document.getElementById('root'));
    

    这次我们主要观察 Container 的结构,它实际上是将 React.createElement() 方法的第三个参数作为 props.children 传递到了组件内部,而这个 children 是一个 Greeting,最终是将 Greeting 渲染在 Container 组件内部。

    接下来,我们要改进一下之前实现的 React.createElement() 和 ReactDOM.render() 方法,使它们支持组件的形式,模拟生成虚拟 DOM 和真实 DOM。

    先来看看 React.createElement() 方法:

    const React = {
      // 创建DOM描述对象 即虚拟DOM
      createElement(type, props, ...children) {
        let propsChildren = children;
    
        // 组件参数的props.children本身是数组
        // 所以调用组件函数时这里需要特殊处理
        if (Array.isArray(children[0])) {
          propsChildren = children[0];
        }
    
        // 结点
        let vnode = {
          type,
          props: {
            ...props,
            children: propsChildren,
          }
        };
    
        // 挂载组件函数体的虚拟DOM
        if (typeof type === 'function') {
          vnode.body = type({
            ...props,
            children,
          });
        }
    
        return vnode;
      }
    };
    

    上面的代码主要对组件做了特殊处理。如果当前处理对象是组件,则对应的 type 就是函数的引用,我们会调用这个组件函数,然后将函数体的结果作为 body 属性挂载到该结点上。需要注意的是,我们在上面方法参数中使用了可变参数的形式,如果直接引用这个 children,它本身就是一个变参数组,如果组件体内使用了 props.children,那么在调用 React.createElement() 时,变参数组的形式将会是 [[...]],所以我们需要特殊处理一下。

    现在,我们运行程序,看看上面代码生成的虚拟 DOM 结构:

    最后,再来看看 ReactDOM.render() 方法:

    const ReactDOM = {
      // 渲染真实DOM
      render(vnode, container) {
        let realDOM = this.generateDOM(vnode);
        container.appendChild(realDOM);
      },
      // 获取真实DOM
      generateDOM(vnode) {
        if (typeof vnode.type === 'function') {
          // 将组件函数体的虚拟DOM生成真实DOM
          return this.generateDOM(vnode.body);
        }
    
        let elem = document.createElement(vnode.type);
        // 特殊key值映射
        let specialKeyMap = {
          className: 'class',
          fontSize: 'font-size',
        };
        let {props} = vnode;
    
        // 设置DOM属性
        props && Object.keys(props).forEach(key => {
          if (key === 'children') {
            // 处理子节点
            props.children.forEach(child => {
              if (typeof child === 'string') {
                // 纯内容节点
                elem.appendChild(document.createTextNode(child));
              } else {
                // DOM节点
                elem.appendChild(this.generateDOM(child));
              }
            });
          } else if (key === 'style') {
            // 设置样式属性
            let styleObj = props.style;
            let styleItems = [];
    
            Object.keys(styleObj).forEach(styleKey => {
              styleItems.push(`${specialKeyMap[styleKey] || styleKey}:${styleObj[styleKey]}`);
            });
    
            elem.setAttribute('style', styleItems.join(';'));
          } else {
            // 设置其他属性
            elem.setAttribute(specialKeyMap[key] || key, props[key]);
          }
        });
    
        return elem;
      }
    };
    

    上面代码中改动较小,我们只添加了几行针对组件的处理逻辑,如果是组件函数,则将函数体的虚拟 DOM 生成真实 DOM。

    最后,我们来看看最终生成的 DOM 结构:

  • 相关阅读:
    System.Threading.Timer 无规律执行次数的问题
    C#通过URL获取顶级域名的方法
    C#变量声明添加?与@的用法
    基于system.diagnostics Trace的日志输出
    SSB(SQLservice Service Broker) 入门实例
    .NET 入门测试题二:流程控制
    .NET 入门测试题三:变量的更多内容
    .NET 入门测试题四:函数
    .NET 入门测试题一:变量与表达式
    WinCE切换GPRS
  • 原文地址:https://www.cnblogs.com/liuhe688/p/10970784.html
Copyright © 2011-2022 走看看