资讯专栏INFORMATION COLUMN

从代码实践潜入React内部,深入diff

leap_frog / 3324人阅读

摘要:概述协调,调解本身不存在公共的。安装的确切结果有时在源代码中称为取决于渲染器,可以是节点,字符串或表示原生视图。关键的缺失部分是对更新的支持。为避免混淆,我们将和的实例叫做内部实例。但是,内部实例树包含复合和主机内部实例。

本节是 stack reconciler程序的实现说明的集合。

本文有一定的技术含量,要对React公共API以及它如何分为核心,渲染器和协调(和解,reconciler)程序有很深的理解。如果你对React代码库不是很熟悉,请首先阅读代码库概述。

它还假设你了解React组件的实例和元素之间的差异。

stack reconciler用于15版本和早期. 它的代码在 src/renderers/shared/stack/reconciler.

视频:从头开始构建React

Paul O’Shannessy谈到了从头开始构建react,这在很大程度上启发了这个文档。

本文档和他的演讲都是对实际代码库的简化,因此你可以通过熟悉它们来获得更好的理解。

概述

reconciler(协调,调解)本身不存在公共的API。像React DOM和React Native这样的渲染器使用它根据用户编写的React组件有效地更新用户界面。

挂载(mounting)作为递归过程

让我们考虑第一次挂载组件:

ReactDOM.render(, rootEl);

React DOM会将传递给调节器(reconciler)。请记住,是一个React元素,即对要呈现的内容的描述。你可以将其视为普通对象(笔者:不了解的可以查看这篇文章):

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

调解器会检查这个App是类还是函数(对于这个得实现可以查看如何知道是函数还是类这篇文章)。

如果App是一个函数,则调解器将调用App(props)来获取渲染元素。

如果App是一个类,那么调解器会通过new App(props)去实例化App,调用componentWillMount生命周期方法,然后调用render方法来获取渲染的元素。

无论哪种方式,调解器都将得知App“渲染到”的元素。

这个过程是递归的。App可能会渲染,可能会渲染

),都没有关系,都会去让渲染器去负责mounting它。

由子组件生成的DOM节点将附加到父DOM节点,并且将递归地组装完整的DOM结构。

注意: 调解器本身与DOM无关。mounting(安装)的确切结果(有时在源代码中称为“mount image”)取决于渲染器,可以是DOM节点(React DOM),字符串(React DOM Server)或表示原生视图(React Native)。

如果我们要扩展代码来处理计算机元素,它将如下所示:

function isClass(type) {
  // 继承自 React.Component 类有一个标签 isReactComponent
  return (
    Boolean(type.prototype) &&
    Boolean(type.prototype.isReactComponent)
  );
}

// 这个函数只处理复合的元素
// 比如像是, 

这是有效的,但仍远未达到协调者的实际运行方式。关键的缺失部分是对更新的支持。

介绍内部实例

react的关键特点是你可以重新渲染所有东西,它不会重新创建DOM或重置状态。

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

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

堆栈协调器代码库通过使mount函数成为一个类上面的方法来解决这个问题。但是这种方法存在一些缺点,我们在正在进行的协调重写任务中正朝着相反的方向去发展(笔者:目前fiber已经出来了)。不过 这就是它现在的运作方式。

我们将创建两个类:DOMComponentCompositeComponent,而不是多带带的mountHostmountComposite函数。

两个类都有一个接受元素的构造函数,以及一个返回已安装节点的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)) {
      // Component class
      publicInstance = new type(props);
      // Set the props
      publicInstance.props = props;
      // Call the lifecycle if necessary
      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;

    // 根据元素实例化子内部实例
    // 他将是DOMComponent,例如
,

// 或者是CompositeComponent,例如

这与我们之前的mountComposite()实现没什么不同,但现在我们可以存储一些信息,例如this.currentElement,this.renderedComponentthis.publicInstance,在更新期间使用。

请注意,CompositeComponent的实例与用户提供的element.type的实例不同。CompositeComponent是我们的协调程序的实现细节,永远不会向用户公开。用户定义的类是我们从element.type读取的,CompositeComponent会创建这个类的实例。

为避免混淆,我们将CompositeComponentDOMComponent的实例叫做“内部实例”。 它们存在,因此我们可以将一些长期存在的数据与它们相关联。只有渲染器和调解器知道它们存在。

相反,我们将用户定义类的实例称为“公共实例(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]);
      }
    });

    // 创建并保存包含的子元素
    // 这些子元素,每个都可以是DOMComponent或CompositeComponent
    // 这些匹配是依赖于元素类型的返回值(string或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));

    // DOM节点作为mount的节点返回
    return node;
  }
}

与上面的相比,mountHost()重构之后的主要区别是现在将this.nodethis.renderedChildren与内部DOM组件实例相关联。我们会用他来用于在后面做非破坏性的更新。

因此,每个内部实例(复合或主机)现在都指向其子级内部实例。为了帮助可视化,如果函数组件呈现

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

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

相关文章

  • react进阶漫谈

    摘要:父组件向子组件之间非常常见,通过机制传递即可。我们应该听说过高阶函数,这种函数接受函数作为输入,或者是输出一个函数,比如以及等函数。在传递数据的时候,我们可以用进一步提高性能。 本文主要谈自己在react学习的过程中总结出来的一些经验和资源,内容逻辑参考了深入react技术栈一书以及网上的诸多资源,但也并非完全照抄,代码基本都是自己实践,主要为平时个人学习做一个总结和参考。 本文的关键...

    neuSnail 评论0 收藏0
  • react进阶漫谈

    摘要:父组件向子组件之间非常常见,通过机制传递即可。我们应该听说过高阶函数,这种函数接受函数作为输入,或者是输出一个函数,比如以及等函数。在传递数据的时候,我们可以用进一步提高性能。 本文主要谈自己在react学习的过程中总结出来的一些经验和资源,内容逻辑参考了深入react技术栈一书以及网上的诸多资源,但也并非完全照抄,代码基本都是自己实践,主要为平时个人学习做一个总结和参考。 本文的关键...

    wyk1184 评论0 收藏0
  • react进阶漫谈

    摘要:父组件向子组件之间非常常见,通过机制传递即可。我们应该听说过高阶函数,这种函数接受函数作为输入,或者是输出一个函数,比如以及等函数。在传递数据的时候,我们可以用进一步提高性能。 本文主要谈自己在react学习的过程中总结出来的一些经验和资源,内容逻辑参考了深入react技术栈一书以及网上的诸多资源,但也并非完全照抄,代码基本都是自己实践,主要为平时个人学习做一个总结和参考。 本文的关键...

    junnplus 评论0 收藏0
  • FE.SRC-React实战与原理笔记

    摘要:异步实战状态管理与组件通信组件通信其他状态管理当需要改变应用的状态或有需要更新时,你需要触发一个把和载荷封装成一个。的行为是同步的。所有的状态变化必须通过通道。前端路由实现与源码分析设计思想应用是一个状态机,视图与状态是一一对应的。 React实战与原理笔记 概念与工具集 jsx语法糖;cli;state管理;jest单元测试; webpack-bundle-analyzer Sto...

    PumpkinDylan 评论0 收藏0
  • 2017文章总结

    摘要:欢迎来我的个人站点性能优化其他优化浏览器关键渲染路径开启性能优化之旅高性能滚动及页面渲染优化理论写法对压缩率的影响唯快不破应用的个优化步骤进阶鹅厂大神用直出实现网页瞬开缓存网页性能管理详解写给后端程序员的缓存原理介绍年底补课缓存机制优化动 欢迎来我的个人站点 性能优化 其他 优化浏览器关键渲染路径 - 开启性能优化之旅 高性能滚动 scroll 及页面渲染优化 理论 | HTML写法...

    dailybird 评论0 收藏0

发表评论

0条评论

leap_frog

|高级讲师

TA的文章

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