React渲染原理

渲染处理

在React中,是将编写的Jsx语法编译为虚拟DOM对象,然后将构建的虚拟DOM对象渲染为真实dom,最后将真实DOM渲染到页面中的过程。

第一次渲染页面是直接从虚拟DOM转化编译为真实DOM,后面每当状态发生改变时,React会生成新的虚拟DOM并与旧的虚拟DOM进行比较,
通过比较新旧虚拟DOM的差异,找出需要更新的部分,并将这些部分更新到真实DOM上。

下面看这段代码

import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <>
    <h1>react Jsx渲染</h1>
    <div>Jsx编译成虚拟DOM,然后将构建的虚拟DOM对象渲染为真实dom,最后将真实DOM渲染到页面中。</div>
  </>
);

上面写的是Jsx代码的格式,React会做些什么呢?

首先React基于 babel-preset-react-app 把JSX编译为 React.createElement(...)这种格式!!
babel-preset-react-app
只要是元素节点,必然会基于createElement进行处理。

然后执行React.createElement函数对象返回虚拟DOM。
在控制台输出结果:

console.log(
  React.createElement(
    React.Fragment, 
    null, 
    React.createElement("h1", {className: "title"}, "react Jsx\u6E32\u67D3"), 
    React.createElement("div", {className: "content"}, "Jsx\u7F16\u8BD1\u6210\u865A\u62DFDOM\uFF0C\u7136\u540E\u5C06\u6784\u5EFA\u7684\u865A\u62DFDOM\u5BF9\u8C61\u6E32\u67D3\u4E3A\u771F\u5B9Edom\uFF0C\u6700\u540E\u5C06\u771F\u5B9EDOM\u6E32\u67D3\u5230\u9875\u9762\u4E2D\u3002")
  )
)

输出
React 虚拟DOM
可以得到这样的一个对象,里面包含了节点类型,属性、子节点等信息。

createElement函数做了什么?看下面这段代码:

//创建虚拟DOM 返回一个虚拟节点对象
function createElement(ele,props,...children){
 const vdom = {
  $$typeof: Symbol('react.element'),
  type:null,//标签名/组件
  props:{}// 存储了元素的相关属性 && 子节点信息
 }
 let len = children.length
 vdom.type = ele
 if(props!==null){
    vdom.props = {
     ...props
    }
    if(len === 1){
       vdom.props.children = children[0]
    }
    if(len > 1){
       vdom.props.children = children
    }
 }
 return vdom
}

ele:元素标签名/组件名
props:元素的属性集合,如果没有设置过任何属性,则此值是null)
children:第三个及以后的参数,都是当前元素的子节点。如果没有子节点则没有这个属性,属性值可能是一个值,也可能是一个数组
以上是利用createElement构建的一个简单的虚拟DOM。

最后就是利用render函数把构建的虚拟DOM渲染为真实DOM。

首先需要一个迭代器,可以获取对象所有的私有属性。代码如下:

function each(obj, callback){
  if(obj === null || typeof obj !== 'object') throw new TypeError('obj is not a object');
  if(typeof callback !== "function") throw new TypeError('callback is not a function');
  let keys = Reflect.ownKeys(obj);
  keys.forEach(key => {
      let value = obj[key];
      // 每一次迭代,都把回调函数执行
      callback(value, key);
  });
}

然后模拟render函数,代码如下:

function render(vdom, container) {
    let { type, props } = vdom;
    // 如果type是string,那一定是标签名称,不是组件
    if (typeof type === "string") {
        // 创建dom标签
        let ele = document.createElement(type);
        // 枚举虚拟DOM对象的所有props属性,为标签设置相关的属性 & 子节点
        each(props, (value, key) => {
            // 如果key===className,那就是标签的class样式名称
            if (key === 'className') {
                ele.className = value;
                return;
            }
            // 如果key===style,就是给标签设置的行内样式对象
            if (key === 'style') {
                each(value, (val, attr) => {
                    ele.style[attr] = val;
                });
                return;
            }
            // 如果key===children,那说明标签存在子节点
            if (key === 'children') {
                let children = value;
                if (!Array.isArray(children)) children = [children];
                children.forEach(child => {
                    // 子节点如果是文本节点,直接在标签内插入即可
                    if (/^(string|number)$/.test(typeof child)) {
                        ele.appendChild(document.createTextNode(child));
                        return;
                    }
                    // 如果子节点又是一个虚拟DOM,就递归处理
                    render(child, ele);
                });
                return;
            }
            // 其他属性设置
            ele.setAttribute(key, value);
        });
        // 把新增的标签,增加到指定容器中
        container.appendChild(ele);
    } else {
      // 如果是组件名称就会执行createElement函数创建虚拟DOM,然后又会执行render函数渲染
      //...
    }
};

最后调用上面的createElementrender函数,生成真实DOM,渲染到浏览器

const vdom = createElement(
  "div",
  {className:"box"},
  createElement("h1",{className:"title"},"标题"),
  createElement("div",{className:"content"},"内容"),
) 

console.log(vdom)//虚拟DOM对象

render(vdom, document.getElementById('root'))

浏览器显示效果:
React 虚拟DOM

总结

  • 基于babel-preset-react-app将调用的组件转化换为React.createElement()格式
  • 执行React.createElement()创建虚拟DOM
  • 把虚拟DOM中props作为实参传递给createElement函数,执行该函数,得到返回结果,然后基于render函数渲染为真实DOM,插入到body里面的root容器中。

一幅图说明一切
React渲染流程图


  转载请注明: 小浩之随笔 React渲染原理

 上一篇
React中组件的props属性 React中组件的props属性
什么是Props props是组件(函数组件和class组件)间的内置属性,用于在组件之间传递数据。它是父组件向子组件传递信息的一种方式,通过 props,父组件可以向子组件传递数据、函数、配置项等。 初次使用Props 先定义一个父组件
2022-04-06
下一篇 
React安装配置通用的Redux React安装配置通用的Redux
这篇文章是建立在我上篇文章《从零开始搭建react项目》基础了配置的哟~,这里我以做一个简单的Todolist来使用Radux哈 一、我们使用Radux,必不可少的一步当然是安装radux 1、执行npm i redux -S 2、执行np
2022-03-19
  目录