资讯专栏INFORMATION COLUMN

react源码解析之stack reconciler

ky0ncheng / 2514人阅读

摘要:争取把源码剖析透学习透。除了用户定义的复合组件外元素还可能表示特定于平台的主机组件。装载的具体结果有时在源代码中称为装载映像取决于渲染器,可能为节点字符串服务器或表示本机视图的数值。其所缺少的关键部分是对更新的支持。

关于源码解读的系列文章,可以关注我的github的这个仓库, 现在才刚刚写,后续有空就写点。争取把react源码剖析透学习透。有不正确的地方希望大家帮忙指正。大家互相学习,共同进步。

本篇文章是官方文档的翻译,英文原文请访问官网

这个章节是stack reconciler的一些实现说明.

它的技术性很强并假定你能完全理解React的公开API,以及它是如何划分为核心、渲染器和协调器的。如果你对React代码不是很熟悉,请先阅读代码概览。

它还假定你能够理解React组件、实例和元素的区别。

Stack reconciler 被用在React 15 以及更早的版本中, 它在源代码中的位置是src/renderers/shared/stack/reconciler.

视频:从零开始构建React

Paul O"Shannessy给出了一个关于从零开始构建React的讨论,在很大程度上对本文档给予了启发。

本文档与上边的视频都是对实际代码库的简化,因此你可以通过熟悉两者来更好地理解。

概述

协调器本身没有公共 API. 但是诸如React DOM 和React Native的渲染器使用它依据用户所编写的React组件来有效地更新用户界面.

以递归过程的形式装载

让我们考虑首次装载组件的情形:

ReactDOM.render(, rootEl);

React DOM会将 传递给协调器。请记住, 是一个React元素,也就是说是对哪些要渲染的东西的说明。你可以把它看成一个普通的对象:

console.log();
// { type: App, props: {} }

协调器(reconciler)会检查 App是类还是函数。如果 App 是函数,协调器会调用App(props)来获取所渲染的元素。如果App是类,协调器则会使用new App(props)创建一个App实例,调用 componentWillMount() 生命周期方法,进而调用 render() 方法来获取所渲染的元素。无论如何,协调器都会学习App元素的“渲染行为”。

此过程是递归的。App 可能渲染为,而可能渲染为

)或是两者兼有。

由子级组件生成的DOM节点将被追加到DOM父节点,同时整的DOM结构会被递归装配。

注意:

协调器本身(reconciler)并不与DOM捆绑。装载(mounting)的具体结果(有时在源代码中称为“装载映像”)取决于渲染器(renderer),可能为 DOM节点(React DOM)、字符串(React DOM服务器)或表示本机视图的数值(React Native)。

我们来扩展一下代码,以处理主机元素(host elements):

function isClass(type) {
  //  React.Component 子类含有这一标志
  return (
    Boolean(type.prototype) &&
    Boolean(type.prototype.isReactComponent)
  );
}

// 该函数仅处理含复合类型的元素。  例如,它处理

该代码能够工作但仍与协调器(reconciler)的真正实现相差甚远。其所缺少的关键部分是对更新的支持。

介绍内部实例

React 的关键特征是您可以重新渲染所有内容, 它不会重新创建 DOM 或重置状态:

ReactDOM.render(, rootEl);
// 应该重新使用现存的 DOM:
ReactDOM.render(, rootEl);

但是, 上面的实现只知道如何装载初始树。它无法对其执行更新, 因为它没有存储所有必需的信息, 例如所有 publicInstance ,
或者哪个 DOM 节点 对应于哪些组件。

堆栈协调(stack reconciler)的基本代码是通过使 mount () 函数成为一个方法并将其放在类上来解决这一问题。
这种方式有一些缺陷,但是目前代码中仍然使用的是这种方式。不过目前我们也正在重写协调器(reconciler)

我们将创建两个类: DOMComponent 和 CompositeComponent , 而不是多带带的 mountHost 和 mountComposite 函数。

两个类都有一个接受 element 的构造函数, 以及一个能返回已装入节点的 mount () 方法。我们将用一个能实例化正确类的工厂函数替换掉之前
例子里的mount函数:

function instantiateComponent(element) {
  var type = element.type;
  if (typeof type === "function") {
    // 用户自定义组件
    return new CompositeComponent(element);
  } else if (typeof type === "string") {
    // 特定于平台的组件
    return new DOMComponent(element);
  }  
}

首先, 让我们考虑如何实现 CompositeComponent:

class CompositeComponent {
  constructor(element) {
    this.currentElement = element;
    this.renderedComponent = null;
    this.publicInstance = null;
  }

  getPublicInstance() {
    // 针对复合组合, 返回类的实例.
    return this.publicInstance;
  }

  mount() {
    var element = this.currentElement;
    var type = element.type;
    var props = element.props;

    var publicInstance;
    var renderedElement;
    if (isClass(type)) {
      // 组件类
      publicInstance = new type(props);
      // 设置属性
      publicInstance.props = props;
      // 如果有必要,调用生命周期
      if (publicInstance.componentWillMount) {
        publicInstance.componentWillMount();
      }
      renderedElement = publicInstance.render();
    } else if (typeof type === "function") {
      // Component function
      publicInstance = null;
      renderedElement = type(props);
    }

    // Save the public instance
    this.publicInstance = publicInstance;

    // 通过element实例化内部的child实例,这个实例有可能是DOMComponent,比如
or

// 也可能是CompositeComponent 比如说 or

这与我们以前的 mountComposite() 实现没有太大的不同, 但现在我们可以保存一些信息,
比如this.currentElementthis.renderedComponentthis.publicInstance ,这些保存的信息会在更新期间被使用。

请注意, CompositeComponent的实例与用户提供的 element.type 的实例不是一回事。
CompositeComponent是我们的协调器(reconciler)的一个实现细节, 从不向用户公开。
用户自定义类是我们从 element.type 读取的,并且通过 CompositeComponent 创建它的一个实例。

为避免混乱,我们将CompositeComponentDOMComponent的实例称为“内部实例”。
由于它们的存在, 我们可以将一些长寿数据(ong-lived)与它们关联起来。只有渲染器(renderer)和协调器(reconciler)知道它们的存在。

另一方面, 我们将用户定义的类的实例称为 "公共实例"(public instance)。公共实例是您在 render() 和自定义组件的其他方法中看到的 this

mountHost() 函数被重构为 DOMComponent 类上的 mount()方法, 也看起来很熟悉:

class DOMComponent {
  constructor(element) {
    this.currentElement = element;
    this.renderedChildren = [];
    this.node = null;
  }

  getPublicInstance() {
    // For DOM components, only expose the DOM node.
    return this.node;
  }

  mount() {
    var element = this.currentElement;
    var type = element.type;
    var props = element.props;
    var children = props.children || [];
    if (!Array.isArray(children)) {
      children = [children];
    }

    // Create and save the node
    var node = document.createElement(type);
    this.node = node;

    // Set the attributes
    Object.keys(props).forEach(propName => {
      if (propName !== "children") {
        node.setAttribute(propName, props[propName]);
      }
    });

    // Create and save the contained children.
    // Each of them can be a DOMComponent or a CompositeComponent,
    // depending on whether the element type is a string or a function.
    var renderedChildren = children.map(instantiateComponent);
    this.renderedChildren = renderedChildren;

    // Collect DOM nodes they return on mount
    var childNodes = renderedChildren.map(child => child.mount());
    childNodes.forEach(childNode => node.appendChild(childNode));

    // Return the DOM node as mount result
    return node;
  }
}

从 mountHost () 重构后的主要区别在于, 我们现在将 this.nodethis.renderedChildren 与内部 DOM 组件实例相关联。
我们还将使用它们在将来应用非破坏性更新。

因此, 每个内部实例 (复合实例或主机实例)(composite or host) 现在都指向内部的子实例。为帮助可视化, 如果功能 组件呈现

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95238.html

相关文章

  • 浅谈React Fiber

    摘要:因为版本将真正废弃这三生命周期到目前为止,的渲染机制遵循同步渲染首次渲染,更新时更新时卸载时期间每个周期函数各司其职,输入输出都是可预测,一路下来很顺畅。通过进一步观察可以发现,预废弃的三个生命周期函数都发生在虚拟的构建期间,也就是之前。 showImg(https://segmentfault.com/img/bVbweoj?w=559&h=300); 背景 前段时间准备前端招聘事项...

    izhuhaodev 评论0 收藏0
  • react源码总览(翻译)

    摘要:每次都信誓旦旦的给自己立下要好好学习源码的,结果都是因为某个地方卡住了,或是其他原因没看多少就放弃了。这次又给自己立个坚持看完源码。我看的源码版本是。本篇文章是官方文档里边的一篇文章的翻译,原文地址。 每次都信誓旦旦的给自己立下要好好学习react源码的flag,结果都是因为某个地方卡住了,或是其他原因没看多少就放弃了。这次又给自己立个flag-坚持看完react源码。为了敦促自己,特...

    Tikitoo 评论0 收藏0
  • 漫谈前端性能 突破 React 应用瓶颈

    摘要:表示调用栈在下一将要执行的任务。两方性能解药我们一般有两种方案突破上文提到的瓶颈将耗时高成本高易阻塞的长任务切片,分成子任务,并异步执行这样一来,这些子任务会在不同的周期执行,进而主线程就可以在子任务间隙当中执行更新操作。 showImg(https://segmentfault.com/img/remote/1460000016008111); 性能一直以来是前端开发中非常重要的话题...

    whlong 评论0 收藏0
  • React Fiber 原理介绍

    摘要:如果运算持续占用主线程,页面就没法得到及时的更新。三解题思路解决主线程长时间被运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。这颗新树每生成一个新的节点,都会将控制权交回给主线程,去检查有没有优先级更高的任务需要执行。 欢迎关注我的公众号睿Talk,获取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 在...

    leap_frog 评论0 收藏0

发表评论

0条评论

ky0ncheng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<