React之虚拟DOM到真实DOM经历了什么

栏目: 服务器 · 发布时间: 5年前

  • 通过 create-react-app easy_react 脚手架创建一个项目
  • 清理 src 目录,这里我们只留下一个 index.js 即可
  • 修改 index.js 如下
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
const style = {
  color: 'red',
  background: 'yellow',
  fontSize:'20px'
}
ReactDOM.render(
  <h1 id={'gu_yan'} className={'gu-yan'} style={style}>
    Guyan
  </h1>, document.getElementById('root'));
复制代码

启动项目

  • 执行 yarn start 页面效果如下
    React之虚拟DOM到真实DOM经历了什么

分析阶段

  • index.js 的代码拷贝到 babel 中进行转换,效果如下
    React之虚拟DOM到真实DOM经历了什么
  • 分析上图
    • 比较代码前后的变化,我们发现变化如下
    /**
     * <h1 id={'gu_yan'} className={'gu-yan'} style={style}>
     *  Guyan
     * </h1>, document.getElementById('root'));
     */
                ‖
                ‖ 
                ‖
                ↓
    React.createElement("h1", {
      id: 'gu_yan',
      className: 'gu-yan',
      style: style
    }, "Guyan")
    复制代码
  • index.js 文件,修改如下,发现页面无变化,控制台打印结果如下图
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
const style = {
  color: 'red',
  background: 'yellow',
  fontSize: '20px'
};
const element = React.createElement("h1", {
  id: 'gu_yan',
  className: 'gu-yan',
  style: style
}, "Guyan")
console.log(element)
ReactDOM.render(element, document.getElementById('root'));
复制代码
React之虚拟DOM到真实DOM经历了什么
  • 如上图可知 React.createElement() 执行返回的结果是一个对象,这个对象我们就称之为虚拟 DOM ,到这里我们就可以总结出虚拟 DOM 的由来了
    • 【1】在 webpack 打包的时候会调用 babel-loader ,将 JSX 语法转义为 React.createElement(...) 的形式
    • 【2】 React.createElement 执行返回一个对象,这对象就是虚拟 DOM
  • 解析 React.createElement 的执行过程
    • 【1】收集属性对象,处理特殊的属性比如 childrenkeyref ;本文只举例说明 children
      • 【1-1】创建一个 props 对象
      • 【1-2】将传进来的第二个参数对象中的每一个属性都挂在 props 上,值为第二个参数对象中相对于的值
      • 【1-3】给 props 挂一个 children 属性,值为传进来的第三个参数
        • 注: children 这个值是一个字符串或者是一个数组。如果执行 React.createElement 的时候传入的参数大于3,那么 children 的值就是一个数组,其值为除前两个之外的所有属性
    • 【2】将传入的 type 和收集的属性对象作为参数传入到 ReactElement 中执行( reactElement(type,props) )
    • 【3】 ReactElement 执行创建一个 newElement 对象
    • 【4】给 newElement 挂一个 $$typeof 属性,这里我们统一将其值赋为 Symbol(react.element)
    • 【5】给 newElement 挂一个 type 属性,值为传进来的 type
    • 【6】给 newElement 挂一个 props 属性,值为传进来的 props
    • 【7】返回 newElement (虚拟 DOM
  • 解析 ReactDom.render 的执行过程
    • 【1】判断传入的虚拟 DOM 的类型
      • 【1.1】普通文本类型,则第一个参数可能是 string 或者 number ,则直接创建一个真实文本节点
      • 【1.2】普通 html 标签组件,比如 <div></div> ,第一个参数对象的 type 属性是一个 string 类型,创建一个真实的 DOM 节点并将第一个参数的 props 属性中的一些普通属性挂载到这个真实的 DOM 节点上,并对一些特殊的属性比如 children 的进行处理,详细见下文的实现阶段
      • 【1.3】函数组件( function Component ),第一个参数对象的 type 属性是一个 function 类型, type 执行传入第一个参数的 props
      • 【1.4】类组件( class Component ),第一个参数对象的 type 属性上有一个 isReactComponent 属性, new type(props) 创建实例,并让实例的 render 方法执行
    • 【2】将创建的真实节点插入到父节点中

实现阶段

react.js

// _react.js

    class Component {
        static isReactComponent = true;
        constructor(props){
            this.props = props;
        }
    }
    
    function ReactElement(type,props){
        const virtual_dom = {};
        virtual_dom.$$typeof = Symbol.for('react.element');
        virtual_dom.type = type;
        virtual_dom.props = props;
        return virtual_dom;
    }

    function createElement(type,config,children){
        const props = {};
        for (const propName in config){
            if (config.hasOwnProperty(propName)){
                props[propName] = config[propName];
            }
        }
        const childrenLength = arguments.length - 2;
        if (childrenLength === 1){
            props.children = children;
        }else if (childrenLength > 2){
            props.children = Array.from(arguments).slice(2);
        }
        return ReactElement(type,props);
    }
    
    export default {createElement,Component}
复制代码

react-dom.js

// _react_dom.js
    
    function render(virtual_dom,parent_node){
        if (typeof virtual_dom ==='string' || typeof virtual_dom === 'number'){
        /**
         *处理直接渲染文本节点的情况,比如ReactDOM.render('guYan', document.getElementById('root'));
         */
         return parent_node.appendChild(document.createTextNode(virtual_dom));
        }
        
        if (virtual_dom.type.isReactComponent){
         /**
         *处理直接渲染Class Component的情况
         */
            virtual_dom = new virtual_dom.type(virtual_dom.props).render();
            render(virtual_dom,parent_node);
        }
        
        if (typeof virtual_dom.type === 'function'){
         /**
         *处理直接渲染function Component的情况
         */
            virtual_dom = virtual_dom.type(virtual_dom.props);
            render(virtual_dom,parent_node);
        }
        let {type , props} = virtual_dom;
        let real_dom = document.createElement(type); // 根据type创建真实节点
        for (const propName in props){ // 对props进行处理
            if(props.hasOwnProperty(propName)){
                const prop =  props[propName];
                if(propName === 'className'){
                    real_dom.className = prop;
                }else if (propName === 'style'){
                    let cssText = Object.keys(prop).map(attr=>(`${attr.replace(/([A-Z])/g,function(){
                        return `-${arguments[1].toLowerCase()}`
                    })}:${prop[attr]}`)).join(';');
                    real_dom.style.cssText = cssText;
                }else if (propName === 'children'){ 
                    /**
                     *children可能是字符串或者是数组,如果是字符串转化成数组,然后递归调用render,需要注意的是
                     * 此时传入的父节点是我们创建的real_dom
                     */
                    let childArr = Array.isArray(prop) ? prop : [prop];
                    childArr.forEach(child=>render(child,real_dom));
                }else {
                    real_dom.setAttribute(propName,prop);
                }
            }
        }
        return parent_node.appendChild(real_dom);
    }
复制代码

写在最后

  • 日常推荐使用函数组件
    • 1.使用简单
    • 2.节约内存
    • 3.提高性能
  • 本文中的仅仅在表面层说明原理。并未深度剖析,如何错误还望指教。谢谢!

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创投42章经

创投42章经

曲凯 / 中信出版集团 / 2018-10-20 / 58.00

《创投42章经》是拥有百万粉丝的微信公众号“42章经”的精选文章合集,全书共分为心法、内功、招式和江湖传奇四部分。 在心法部分,读者可以学到一些创业与投资的底层思维方式;在内功部分,读者可以了解到,投资人看待一家公司经营状况的标准;在招式部分,读者可以看到作者作为一名资深投资人和睿智的观察者,对过去几年主要的公司、模式以及风口的判断;最后的江湖传奇部分,作者通过一些故事,帮助读者更好地理解当......一起来看看 《创投42章经》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器