在React的幕后 我们从头开始编写实现

在本系列文章中,我们将从头开始创建自己的React实现。 最后,您将了解React的工作方式,调用它的组件生命周期的哪些方法以及原因。 本文面向那些已经使用过React并想了解其设备的人,或者非常好奇的人。

图片

本文是React Internals的翻译,第一部分:基本渲染

这实际上是五分之二的第一篇文章


  1. 渲染基础<-我们在这里
  2. ComponentWillMount和componentDidMount
  3. 更新资料
  4. setState
  5. 交易次数

材料是在与React 15.3相关时创建的,尤其是使用ReactDOM和堆栈协调器。 React 16及更高版本有一些更改。 但是,该材料仍然具有相关性,因为它提供了有关“幕后”情况的一般信息。

第1部分。渲染基础


元素和组件


React中有三种类型的实体:本机DOM元素,虚拟React元素和组件。

本机DOM元素


这些是浏览器用来创建网页的DOM元素,例如div,span,h1。 React通过调用document.createElement()创建它们,并使用基于浏览器的DOM API方法(例如element.insertBefore(),element.nodeValue等)与页面进行交互。

虚拟反应元件


虚拟React元素(通常简称为“元素”)是一个javascript对象,其中包含创建或更新本机DOM元素或此类元素树的必要属性。 基于虚拟React元素,创建本地DOM元素,例如div,span,h1等。 我们可以说虚拟的React元素是用户定义的复合组件的一个实例,下面对此进行了更多介绍。

组成部分


组件是React中一个相当笼统的术语。 组件是React可以进行各种操作的实体。 不同的组件用于不同的目的。 例如,ReactDom库中的ReactDomComponent负责React元素与其对应的本机DOM元素之间的链接。

定制化合物成分


您很可能已经遇到过此类组件。 当您调用React.createClass()或通过扩展React.Component使用ES6类时,您将创建一个自定义的复合组件。 这样的组件具有生命周期方法,例如componentWillMount,shouldComponentUpdate等。 我们可以重新定义它们以添加某种逻辑。 另外,还会创建其他方法,例如mountComponent,receiveComponent。 这些方法仅由React用于其内部目的;我们不会以任何方式与其进行交互。

ZanudaMode =开
实际上,用户创建的组件最初并不完整。 React将它们包装在ReactCompositeComponentWrapper中,后者将所有生命周期方法添加到我们的组件中,之后React可以对其进行管理(插入,更新等)。

反应声明式


对于自定义组件,我们的任务是定义这些组件的类,但是我们不实例化这些类。 React会在需要时创建它们。

同样,我们不使用命令式样式显式创建元素;相反,我们使用JSX以声明式样式编写:

class MyComponent extends React.Component { render() { return <div>hello</div>; } } 

带有JSX标记的代码被编译器翻译为以下代码:

 class MyComponent extends React.Component { render() { return React.createElement('div', null, 'hello'); } } 

也就是说,从本质上讲,它变成了通过显式调用React.createElement()创建元素的命令结构。 但是这种构造是在render()方法内部,我们没有显式调用它,React会在必要时自己调用此方法。 因此,感知React就是声明性的:我们描述我们想要接收的内容,然后React确定如何做。

写你的小React


在获得必要的技术基础之后,我们将开始创建自己的React实现。 这将是一个非常简化的版本,我们称它为Feact。

假设我们要创建一个简单的Feact应用程序,其代码如下所示:

 Feact.render(<h1>hello world</h1>, document.getElementById('root')); 

首先,让我们谈谈JSX。 这正是“撤退”,因为JSX解析是一个单独的重要主题,在我们的Feact实现中,我们将省略它。 如果我们处理的是JSX,我们将看到以下代码:

 Feact.render( Feact.createElement('h1', null, 'hello world'), document.getElementById('root') ); 

也就是说,我们使用Feact.createElement而不是JSX。 因此我们实现了这种方法:

 const Feact = { createElement(type, props, children) { const element = { type, props: props || {} }; if (children) { element.props.children = children; } return element; } }; 

返回的元素是一个简单的对象,表示我们要呈现的内容。

Feact.render()有什么作用?


通过调用Feact.render(),我们传递了两个参数:我们要渲染的内容和位置。 这是任何React应用程序的起点。 让我们为Feact编写render()方法的实现:

 const Feact = { createElement() { /*   */ }, render(element, container) { const componentInstance = new FeactDOMComponent(element); return componentInstance.mountComponent(container); } }; 

render()完成后,我们得到一个完整的网页。 DOM元素由FeactDOMComponent创建。 让我们来编写其实现:

 class FeactDOMComponent { constructor(element) { this._currentElement = element; } mountComponent(container) { const domElement = document.createElement(this._currentElement.type); const text = this._currentElement.props.children; const textNode = document.createTextNode(text); domElement.appendChild(textNode); container.appendChild(domElement); this._hostNode = domElement; return domElement; } } 

mountComponent方法创建一个DOM元素并将其存储在this._hostNode中。 我们现在不会使用它,但是在以下部分中我们将返回到此。

可以在小提琴中查看该应用程序的当前版本。

从字面上看,40行代码足以构成React的原始实现。 我们创造的Feact不可能征服世界,但是它很好地反映了React背后正在发生的事情的本质。

添加自定义组件


我们的功能不仅应该能够呈现HTML中的元素(div,span等),而且还可以呈现用户定义的复合组件:
前面介绍的Feact.createElement()方法目前还可以,因此在代码清单中不再赘述。
 const Feact = { createClass(spec) { function Constructor(props) { this.props = props; } Constructor.prototype.render = spec.render; return Constructor; }, render(element, container) { //      //   , //    } }; const MyTitle = Feact.createClass({ render() { return Feact.createElement('h1', null, this.props.message); } }; Feact.render({ Feact.createElement(MyTitle, { message: 'hey there Feact' }), document.getElementById('root') ); 

让我提醒您,如果可以使用JSX,则调用render()方法将如下所示:

 Feact.render( <MyTitle message="hey there Feact" />, document.getElementById('root') ); 

我们将自定义组件类传递给createElement。 虚拟React元素可以表示常规DOM元素或自定义组件。 我们将区分它们如下:如果我们传递字符串类型,那么这是一个DOM元素; 如果是函数,则此元素表示自定义组件。

改进Feact.render()


如果您现在仔细查看代码,将会看到Feact.render()无法处理自定义组件。 让我们解决这个问题:

 Feact = { render(element, container) { const componentInstance = new FeactCompositeComponentWrapper(element); return componentInstance.mountComponent(container); } } class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); const element = componentInstance.render(); const domComponentInstance = new FeactDOMComponent(element); return domComponentInstance.mountComponent(container); } } 

我们为传递的项目创建了一个包装器。 在包装器内部,我们创建用户组件类的实例,并调用其componentInstance.render()方法。 该方法的结果可以传递给FeactDOMComponent,在该处将创建相应的DOM元素。

现在我们可以创建和渲染自定义组件。 Feact将基于自定义组件创建DOM节点,并根据我们的自定义组件的属性(属性)对其进行更改。 这是我们功能上的重大改进。
请注意,FeactCompositeComponentWrapper直接创建FeactDOMComponent。 这样的亲密关系是不好的。 我们稍后将解决此问题。 如果React具有相同的紧密连接,则只能创建Web应用程序。 添加额外的ReactCompositeComponentWrapper层可以使您分离用于管理虚拟元素的React逻辑和本机元素的最终显示,这不仅使您可以在创建Web应用程序时使用React,而且还可以在移动应用程序中使用React Native。

自定义组件增强


创建的自定义组件只能返回本机DOM元素,如果尝试返回其他自定义组件,则会出现错误。 纠正此缺陷。 想象一下,我们希望执行以下代码而不会出错:

 const MyMessage = Feact.createClass({ render() { if (this.props.asTitle) { return Feact.createElement(MyTitle, { message: this.props.message }); } else { return Feact.createElement('p', null, this.props.message); } } } 

自定义组件的render()方法可以返回本机​​DOM元素或另一个自定义组件。 如果asTitle属性为true,则FeactCompositeComponentWrapper将返回发生错误的FeactDOMComponent的自定义组件。 修复FeactCompositeComponentWrapper:

 class FeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); let element = componentInstance.render(); while (typeof element.type === 'function') { element = (new element.type(element.props)).render(); } const domComponentInstance = new FeactDOMComponent(element); domComponentInstance.mountComponent(container); } } 

实际上,我们现在已经满足了当前的需求。 调用render方法将返回子组件,直到返回本机DOM元素。 这是不好的,因为此类子组件不会参与生命周期。 例如,在这种情况下,我们将无法实现componentWillMount调用。 我们稍后将解决此问题。

再次我们修复Feact.render()


Feact.render()的第一个版本只能处理本地DOM元素。 现在,只有用户定义的组件才能得到正确处理,而无需本机支持。 有必要处理这两种情况。 您可以编写一个工厂,该工厂将根据传递的元素的类型来创建组件,但是React选择了不同的方式:只需将任何传入的组件包装在另一个组件中:

 const TopLevelWrapper = function(props) { this.props = props; }; TopLevelWrapper.prototype.render = function() { return this.props; }; const Feact = { render(element, container) { const wrapperElement = this.createElement(TopLevelWrapper, element); const componentInstance = new FeactCompositeComponentWrapper(wrapperElement); //   } }; 

TopLevelWrapper本质上是一个自定义组件。 也可以通过调用Feact.createClass()进行定义。 它的render方法只是返回传递给它的元素。 现在,每个元素都包装在TopLevelWrapper中,FeactCompositeComponentWrapper将始终接收自定义组件作为输入。

第一部分结论


我们已经实现了Feact,可以渲染组件。 生成的代码显示了渲染的基本概念。 React中的真实渲染要复杂得多,它涵盖事件,焦点,窗口滚动,性能等。

第一部分的最后jsfiddle

Source: https://habr.com/ru/post/zh-CN458916/


All Articles