react渲染原理是什么?渲染原理分析!

猿友 2021-06-25 17:49:20 浏览数 (3899)
反馈

最近有很多人都在说有关于react的原理是什么这个话题,那么今天我们就对“react渲染原理是什么”这个问题来进行分析,下面是小编分享的相关的内容,希望对大家有所帮助!


一、JSX

那么首先我们来看一下,简单的React组件,代码如下:

import React from 'react';

export default function App() {
  return (
    <div className="App">
      <h1>Hello React</h1>
    </div>
  );
}

在这个代码中我们用的语法被称为 JSX,它是​React.createElement​方法的语法糖,我们通过使用 JSX 可以直观的展现 UI 及交互可以实现关注点分离,而且每一个​react​组价的顶部都要导入React,因为​JSX​实际上依赖的是​Babel​(@bable/preset-react)从而来对语法进行转换,最终生成我们需要的​React.createElement​的嵌套语法。下面我们来看下 JSX 转换渲染后的结果吧,代码如下:

function App() {
  return React.createElement(
    'div',
    {
      className: 'App',
    },
    React.createElement('h1', null, 'Hello React')
  );
}

    

二、createElement

createElement()​方法定如下:

React.createElement(type, [props], [...children]);

createElement()​接收三个参数,在代码中我们可以知道它分别是元素类型、属性值和子元素这三个值,而且它最终会生成Virtual DOM,我们现在将​<app/>​组件内容打印到我们的控制台中,如下所示:

app组件获取值

我们通过截图可以看到 Virtual DOM 本质上是 JS 对象,所以我们将节点信息通过键值对的方式存储起来,同时使用嵌套来表示节点间的层级关系。然后再使用 VDOM 能够避免频繁的进行 DOM 操作,同时也为后面的 ​React Diff ​算法创造了条件。那么我们现在回到我们的createElement()方法中,来看一下它是如何生产 VDOM 的。


三、createElement()方法精简版

有关于createElement()方法精简版的代码截图如下:

精简版

在截图中,首先我们通过​createElement()​方法会先通过遍历​config​获取所有的参数,然后获取其子节点以及默认的​Props​的值,然后我们在将值传递给​ReactElement()​调用返回JS对象。如下所示:

调用

在截图中值得我们去注意的是,每个​react​组件都会使用​$$typeof​来进行标识,它的值使用了​Symbol​数据结构来确保唯一性。


四、ReactDOM.render

通过上面的步骤,我们得到了VDOM,react通过协调算法(reconciliation)去比较更新前后的VDOM,从而找到需要更新的最小操作,来减少多次操作DOM的成本,由于我们遍历组件树,当组件越来越大我们的递归遍历成本就会越高所有我们有了下面这种解决方法。

render()​方法:

ReactDOM.render(element, container[, callback])

这边的话我们还需要了解​ReactDOM.render​是怎么构建​fiber tree​,其实呢在ReactDOM.render​中实际调用了legacyRenderSubtreeIntoContainer这个方法,下面是有关的调用过程,代码如下:

ReactDOM = {
  render(element, container, callback) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback
    );
  },
};

在代码中的elementcontainer我想大家都很熟悉,然而在代码中的callback是用来渲染完成后需要执行的回调函数。

接下来我们再来看看该方法的定义,代码如下:

function legacyRenderSubtreeIntoContainer(
  parentComponent,
  children,
  container,
  forceHydrate,
  callback
) {
  let root = container._reactRootContainer;
  let fiberRoot;
  // 初次渲染
  if (!root) {
    // 初始化挂载,获得React根容器对象
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate
    );
    fiberRoot = root._internalRoot;

    // 初始化安装不需要批量更新,需要尽快完成
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;

    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

我们可以发现到,在代码中因为挂载是​root​,所以我们需要将parentComponent的值设置为null

除此之外对于另一个参数​forceHydrate​代表是否是服务端渲染,因为在这边调用了​render()​方法为客户端渲染,所以默认为false。

因为是首次挂载,所以​root​从​container._reactRootContainer​获取不到值,就会创建​FiberRoot​对象。而且在​FiberRoot​对象创建过程中考虑到了服务端渲染的情况,并且函数之间相互调用非常多,所以这里直接展示其最终调用的核心方法,代码如下所示:

// 创建fiberRoot和rootFiber并相互引用
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
  const root = new FiberRootNode(containerInfo, tag, hydrate);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // 创建fiber tree的根节点,即rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);

  return root;
}

我们从代码中可以知道,在这个方法中​containerInfo​就是​root​节点,然而​tag​为​FiberRoot​节点的标记,这里变为​LegacyRoot​。而且另外两个参数和服务端渲染是有关的,在代码中这里使用​FiberRootNode​方法创建了​FiberRoot​对象,并使用​createHostRootFiber​方法创建​RootFiber​对象,使​FiberRoot​中的​current​指向​RootFiber​,​RootFiber​的​stateNode​指向​FiberRoot​,从而形成相互引用。

下面的两个构造函数是展现出了​fiberRoot​以及​rootFiber​的部分重要的属性。

FiberRootNode部分属性,代码如下:

function FiberRootNode(containerInfo, tag, hydrate) {
  // 用于标记fiberRoot的类型
  this.tag = tag;
  // 指向当前激活的与之对应的rootFiber节点
  this.current = null;
  // 和fiberRoot关联的DOM容器的相关信息
  this.containerInfo = containerInfo;
  // 当前的fiberRoot是否处于hydrate模式
  this.hydrate = hydrate;
  // 每个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中
  this.callbackNode = null;
  // 当前任务的优先级
  this.callbackPriority = NoPriority;
}

Fiber Node构造函数的部分属性代码如下:

function FiberNode(tag, pendingProps, key, mode) {
  // rootFiber指向fiberRoot,child fiber指向对应的组件实例
  this.stateNode = null;
  // return属性始终指向父节点
  this.return = null;
  // child属性始终指向第一个子节点
  this.child = null;
  // sibling属性始终指向第一个兄弟节点
  this.sibling = null;
  // 表示更新队列,例如在常见的setState操作中,会将需要更新的数据存放到updateQueue队列中用于后续调度
  this.updateQueue = null;
  // 表示当前更新任务的过期时间,即在该时间之后更新任务将会被完成
  this.expirationTime = NoWork;
}

最终生成的fiber tree结构示意图如下:

示意图

五、React Diff 算法

对于react来说并不会比原生操作的​DOM​快,但是在大型的应用中,我们往往是不需要每次都进行重新渲染的,所以这时候可以让react通过​VCOM ​以及​diff​算法能够值更新必要的​DOM​。


总结:

以上就是有关于“react渲染原理是什么?”这个问题的相关内容,希望对大家有所帮助,当然如果你觉得有更好的认识也可以提出来和大家一同分享,更多与react相关的课程和学习资料我们都可以在W3cschool中进行学习和了解。


1 人点赞