React飞行日记(五) - 重构React渲染函数React.createElement与ReactDOM.render

为什么在JS文件中用那么几行神奇的代码, 就能够把虚拟DOM转换成真实DOM?并且还实实在在的插入了页面中?
为了搞明白这一切, 我自己写了两个方法, 也就是对React.createElementReactDOM.render方法的重构, 来揭露其到底是怎么实现的!

方法实现

  • 实现ReactDOM.render

    /**
     * 简单实现 React.createElement(type, props, ...children) 方法
     *   该方法会创建一个虚拟DOM树, 通过ReactDOM.render()可以把虚拟DOM树实体化成DOM并插入页面中
     *
     * @param {创建的DOM的类型} type
     * @param {创建的DOM的一些属性, 比如id, className, style等等} props
     * @param {创建的DOM的子元素以及子标签(字符串|子DOM)} children
     * @returns 虚拟DOM树
     * 
     * 虚拟DOM树结构:
     *  {
     *    type: 'div',
     *    props: {
     *      id: 'one',
     *      className: 'two',
     *      style: {
     *        color: 'red',
     *        fontSize: '50px'
     *        ...
     *      },
     *      children: ['aaa', [Object], [Object]...]
     *    }
     *    __proto__: Object
     *  }
     */
    function createElement(type, props, ...children) {
      props = props || {}
      let obj = {
        type: type,
        ref: props.ref || null,
        key: props.key || null,
        props: {
          ...props,
        }
      }
      props.ref ? delete obj.props.ref : null
      props.key ? delete obj.props.ref : null
      children && Array.isArray(obj.props.children) ? obj.props.children.append(children) : obj.props.children = children
      return obj
    }
    
  • 实现React.render方法

    // 1.1版本
    /**
     * 简单实现ReactDOM.render(obj, container, callback):
     *  该方法会
     *    1. 把obj[虚拟DOM树]进行DOM实体化, 
     *    2. 把实体化DOM插入container(页面中的元素), 
     *    3. 最后触发回调函数callback
     *
     * @param {虚拟DOM树} [obj={}]
     * @param {DOM被插入的位置} container
     * @param {回调函数} callback
     * @returns  根据虚拟DOM树生成真实DOM并插入页面中
     */
    function render(obj = {}, container, callback) {
      let {
        type, // 生成的DOM类型[function|字符串]
        props // 各种属性 {style:{color: 'red'...}, children: ['我是div标签', {}]}
      } = obj, elem
    
      function vir2DOM(VirDOMObj) {
        if (!VirDOMObj) {
          return
        }
        /**
         *  handleChildren: 把child(字符串|虚拟DOM对象)都转换成XML字符串, 并返回
         *  
         * @param {children遍历传入的单个child对象} child
         * @param {XML字符串} XMLString
         * @returns
         */
        function handleChildren(child, XMLString) {
    
          if (typeof child === 'string' || typeof child === 'number') {
            // 1. 如果是 String|NUmber, 为html中文本对象
            XMLString += child
          } else if (Object.prototype.toString.call(child) === '[object Object]') {
            // 2. children为抽象DOM对象
            /**
             * 把DOM对象转换成字符:
             *    1. 创建一个div标签
             *    2. 把想要转换的DOM对象插入div中
             *    3. 获取div的innerHTML => 就是字符串的DOM对象
             */
            const div = document.createElement('div')
            div.appendChild(vir2DOM(child))
            XMLString += div.innerHTML
          } else if (Object.prototype.toString.call(child) === '[object Array]') {
            // 3. children为数组(里面是children子元素)  ['我是div标签', {type:'div', props: {...}}]
            XMLString += child.map(item => handleChildren(item, '')).join('')
          }
          return XMLString
        }
    
        // 把虚拟DOM树转换成真实DOM
        let {
          type, // 生成的DOM类型[组件function|字符串(div|p|span...)]
          props // 生成的DOM各种属性 {style:{color: 'red'...}, children: ['我是div标签', {}]}
        } = VirDOMObj
    
        let elem = document.createElement(type)
    
        for (let k in props) {
          if (k === 'style') {
            // 1. pops.style  {color: 'red', fontSize:'50px', ...}
            const style = props[k]
            for (let styleName in style) {
              elem.style[styleName] = style[styleName]
            }
          } else if (k === 'children') {
            // 2. props.children {'aaa', {type: div': style:{...}, children:{...}}}
            const children = props[k]
            let resInnerHtml = `` // XML字符串, 把所有子元素转换成XML字符串,最后绑定到innerHTML中
            for (let child in children) {
              // 遍历Children对象, 并交给handleChildren处理子元素
              resInnerHtml = handleChildren(children[child], resInnerHtml)
            }
            elem.innerHTML = resInnerHtml
          } else {
            // 3. 挂载在DOM上的属性, 如id, className, ref, key这些等等
            elem[k] = props[k]
          }
        }
        return elem
      }
    
      // 1. 把obj[虚拟DOM树]进行DOM实体化, 
      if (typeof type === 'function') {
        // 1. type 如果是函数组件
        elem = vir2DOM(type(props)) // 1. 执行传入的组件函数获取虚拟DOM树 2. 转换成真实DOM
      } else {
        // 2. type是字符串(div|p|span这些)
        elem = vir2DOM(obj) // 虚拟DOM对象转换成真实DOM
      }
    
      // 2. 把实体化DOM插入container(页面中的元素)
      if (elem) {
        container ? container.appendChild(elem) : document.querySelector('#root').appendChild(elem)
      }
    
      // 3. 最后触发回调函数callback
      callback && setTimeout(callback)
    
      return elem
    }
    

方法测试

  • 准备两个文件
    在这里插入图片描述
  1. index.js代码

    function Login(props) {
      var type = props.type,
        content = props.content,
        children = props.children;
      // console.log(content); // => 自己写的样式
    
      var myCss = {
        width: '50%',
        margin: '0 auto 10px',
        padding: '0'
      }; // => 传入的类型处理
    
      var typeValue = type || '系统提示';
    
      if (typeof type === 'number') {
        switch (type) {
          case 0: {
            typeValue = '系统提示';
            break;
          }
    
          case 1: {
            typeValue = '系统错误';
            break;
          }
    
          case 2: {
            typeValue = '系统警告';
            break;
          }
    
          default: {
            typeValue = '真香';
            break;
          }
        }
      }
    
      return createElement("section", {
        className: "panel panel-default col-lg-4",
        style: myCss
      }, createElement("div", {
        className: "panel-heading"
      }, createElement("h3", {
        className: "panel-title"
      }, typeValue)), createElement("div", {
          className: "panel-body"
        }, content // children.map(item => item)
      ), children ? createElement("div", {
        className: "panel-footer"
      }, Array.prototype.map.call(children, function (item) {
        return item;
      })) : null);
    }
    
    function fn() {
      return createElement(Login, {
        type: '请登录',
        content: createElement("div", null, createElement("div", {
          className: "input-group"
        }, createElement("span", {
          className: "input-group-btn"
        }, createElement("button", {
          className: "btn btn-default",
          type: "button"
        }, "User")), createElement("input", {
          type: "text",
          className: "form-control",
          placeholder: "\u8BF7\u8F93\u5165\u7528\u6237\u540D"
        })), createElement("p", {
          style: {
            height: '2px'
          }
        }), createElement("div", {
          className: "input-group"
        }, createElement("span", {
          className: "input-group-btn"
        }, createElement("button", {
          className: "btn btn-default",
          type: "button"
        }, "Pswd")), createElement("input", {
          type: "text",
          className: "form-control",
          placeholder: "\u8BF7\u8F93\u5165\u5BC6\u7801"
        })))
      }, createElement("button", {
        className: "btn btn-info",
      }, "\u767B\u5F55"), createElement("button", {
        className: "btn btn-danger",
        style: {
          marginLeft : '10px'
        }
      }, "\u53D6\u6D88"));
    }
    
    
    let res = fn()
    render(res, document.querySelector('body'), () => {console.log('ok')})
    
  2. html.js代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>测试</title>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    </head>
    <body>
      <div id="root"></div>
      <script src="./index.js"></script>
    </body>
    </html>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值