JSX和React创建虚拟DOM、初始渲染的实现原理

150 阅读3分钟

什么是React

  • React是一个用于构建用户界面的JavaScript库,核心专注于视图,目的实现组件化开发

创建项目

  • create-react-app xx
  • cd xx
  • npm run start

什么叫JSX

  • JSX其实只是一种语法糖,最终会通过babel.js转译成React.createElement语法
  • React元素事实上是普通的JS对象,用来描述你在屏幕上看到的内容
  • ReactDOM来确保浏览器中的真是DOM数据和React元素保持一致

JSX

    <h1 className="title" style={{color:'red'}}>hello

转译后的代码

    React.createEelement("h1",{

            className:"title",

            style:{

                color:"red"

            }

    ), "hello"};

返回的结果

    {

        type:"h1",

        props:{

            className:"title",

            style:{

                color:"red"

            }

        },

        children:"hello"

    }

关系图

Image.png

下面我们开始实现

转换

Image.png

  • 从上面jsx转换成下面,借助的是babel插件plugin-transform-react-jsx,react中会首先进行这种转换,然后经过createElement方法处理成虚拟dom返回

创建react.js文件,导出包含createElement方法的对象

Image.png

我们通过使用官方的react打印createElement处理后的结果如下

Image.png

删除没什么用的内部属性,保留ref和key

Image.png

如果父元素有多个儿子,那么会往createElement的参数后面一个一个追加,所以我们要处理一下children参数问题

Image.png

  • 如果参数大于3说明不止一个儿子
  • 否则就只有一个儿子,直接赋值
  • 这里我们为什么用截取参数的方式而不是用剩余运算符呢?
  • 答案就是,在react源码中,如果没有儿子就是undifined,有一个儿子就会处理成一个对象,有多个儿子就会处理成数组。而剩余运算符得到的一定是数组,就和react源码中不一样了

src下创建constants.js文件,用来存放一些常量

Image.png

createElement方法最后我们就拼一个虚拟dom返回

Image.png

我们现在引入自己写的react来看效果

Image.png

打印结果

Image.png

react源码中如果children是一个字符串,那么结果就是一个字符串,不会处理成元素

Image.png

但是我们自己实现的react,为了之后DOM-DIFF方便,我们需要处理一下,把文本字符串包裹成元素

Image.png

  • 此逻辑在react源码里是没有的,是我们为了后面方便DOM-DIFF方便添加的

在createElement方法中,对children进行包装

Image.png

至此我们拿到了虚拟DOM

下面开始写渲染逻辑,也就是react-dom

Image.png

创建react-dom.js文件,导出包含render方法的对象

Image.png

首先,调用createDOM把虚拟dom处理成真实dom,然后插入到容器中

Image.png

createDOM方法

Image.png

  • 拿到type和props
  • 判断type类型,如果是REACT_TEXT,就创建文本节点
  • 否则就创建普通元素
  • 判断如果存在props,那么我们就更新属性

updateProps方法

Image.png

  • 遍历新属性,如果key是一个children,那么就跳过,因为儿子不在这里处理
  • 如果属性是style,那么就循环给dom赋属性
  • 否则就直接赋值,默认处理,我们写的虚拟dom里的属性一般跟真实属性是一一对应的

然后遍历老的属性

Image.png

  • 如果老属性在新属性里没有,那么就删掉

然后我们再回到createDOM方法中

  • 在调用updateProps方法更新属性后我们还没处理children
  • 判断props的children是否是一个对象并且type有值,如果条件成立,就调用render方法,把儿子也变成真实DOM,这是处理一个儿子的情况
  • 然后判断儿子是否是一个数组,这是判断多个儿子的情况,条件如果成立,那么我们就调用reconcileChildren,传入children和dom

reconcileChildren方法

Image.png

  • 循环调用render处理children

至此,我们实现了自己的react创建虚拟DOM、初始渲染的逻辑