温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

react渲染原理怎么实现

发布时间:2022-02-25 10:14:39 来源:亿速云 阅读:175 作者:iii 栏目:开发技术

本篇内容主要讲解“react渲染原理怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“react渲染原理怎么实现”吧!

一、JSX

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

import React from 'react';

export default function App() {
  return (
    <div className="App">
      <h2>Hello React</h2>
    </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('h2', null, 'Hello React')
  );
}

二、createElement

createElement()方法定如下:

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

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

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


三、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。

因为是首次挂载,所以rootcontainer._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节点,然而tagFiberRoot节点的标记,这里变为LegacyRoot。而且另外两个参数和服务端渲染是有关的,在代码中这里使用FiberRootNode方法创建了FiberRoot对象,并使用createHostRootFiber方法创建RootFiber对象,使FiberRoot中的current指向RootFiberRootFiberstateNode指向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渲染原理怎么实现”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI