超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

React 内部原理,第一部分:基础渲染

原文:React Internals, Part One: basic rendering

In this five part series, we will “recreate” React from the ground up, learning how it works along the way. Once we’ve finished, you should have a good grasp of how React works, and when and why it calls the various lifecycle methods of a component.

在这五部分系列中,我们将从头开始“重新创建” React,学习其如何工作。一旦完成,你应该对 React 的工作原理有一个很好地掌握,以及它在何时,为何调用组件的各种生命周期方法。

本系列(The series)

全部译文:

声明(disclaimer)

This series is based on React 15.3, in particular using ReactDOM and the stack reconciler. The fancy new fiber reconciler is out of scope here. The React clone we are going to build will not even come close to implementing all of React. But Feact’s source code will mirror React’s as much as possible.

本系列基于 React 15.3,特别是使用 ReactDOM 和堆栈 reconciler。这款高性能的 fiber reconciler 超出了本文的范围。我们要构建的 React 克隆版(Feact)不会实现所有的 React 功能。但是,Feact 的源代码将尽可能地反映 React 的内容。

一些背景:元素和组件(Some Background: Elements and Components)

At the heart of React are three different types of entities: native DOM elements, virtual elements and components.

React 的核心是三种不同类型的实体:原生 DOM 元素,虚拟元素和组件。

原生 DOM 元素(native DOM elements)

These are exactly what they sound like, the actual DOM elements that the browser uses as the building blocks of a webpage. At some point, React will call document.createElement() to get one, and use the browser’s DOM api to update them such as element.insertBefore(), element.nodeValue, etc.

正如它的名字所说,原生 DOM 元素是浏览器用作构建网页块的实际 DOM 元素。在某些时候,React会调用 document.createElement() 来创建它,并使用浏览器的 DOM API 如 element.insertBefore()element.nodeValue 等来更新它们。

虚拟 React 元素(virtual React elements)

A virtual React element (just called an “element” in the source code), is an in memory representation of what you’d like a given DOM element (or entire tree of elements) to be for a particular render. An element can either directly represent a DOM element such as h1, div, etc. Or it can represent a user defined composite component, which is explained below.

虚拟的 React 元素(在源代码中称为“元素”),是表示特定渲染中给定的DOM元素(或整个元素树)在内存中的表示形式。一个React 元素可以直接表示一个 DOM 元素,如h1div等,或者表示一个用户定义的复合组件,这将在下面解释。

组件(Components)

“Component” is a pretty generic term in React. They are entities within React that do various types of work. Different types of components do different things. For example, ReactDOMComponent from ReactDOM is responsible for bridging between React elements and their corresponding native DOM elements.

“组件”是 React 中相当通用的术语。它们是 React 中的执行不同类型工作的实体。不同类型的组件做不同的事情。例如,ReactDOM 的 ReactDOMComponent 负责在 React 元素及其对应的原生 DOM 元素之间进行连接。

用户定义的复合组件(User Defined Composite Components)

You are already familiar with one type of component: the composite component. Whenever you call React.createClass(), or have an es6 class extend React.Component, you are creating a Composite Component class. It turns out our view of the component lifecycle with methods like componentWillMount, shouldComponentUpdate is just one piece of the puzzle. These are the lifecycle methods that we hook into because they benefit us. But React components have other lifecycle methods such as mountComponent and receiveComponent. We never implement, call, or even know these other lifecycle methods exist. They are only used internally by React.

你已经熟悉一种类型的组件:复合组件。无论何时调用 React.createClass() ,或者有一个 es6 类扩展了 React.Component,都将创建一个复合组件类。我们经常所看到的组件生命周期的方法,如 componentWillMountshouldComponentUpdate 只是它的一部分,这些我们经常使用的生命周期方法,因为他们很有帮助。但是 React 组件还具有其他生命周期方法,如 mountComponentreceiveComponent。我们从来没有实现,调用,甚至知道这些其他的生命周期方法存在。它们仅在 React 内部被使用。

The truth is the components we create are incomplete. React will take our component class, and wrap it in a ReactCompositeComponentWrapper, which then gives the components we wrote the full lifecycle hooks and ability to participate in React.

事实是我们创建的组件是不完整的。 React 将我们的组件类包装在 ReactCompositeComponentWrapper 中,然后给出了我们可以编写完整生命周期钩子和参与 React 的能力的组件。

React 是声明式的(React is declarative)

When it comes to components, our job is to define component classes. But we never instantiate them. Instead React will instantiate an instance of our classes when it needs to.

当开始编写组件的时候,我们的工作就是定义组件类。但是我们从不实例化它们。相反,React 会在需要时实例化类的实例。

We also don’t consciously instantiate elements. But we do implicitly when we write JSX, such as:

我们也不必实例化元素。但是,当我们编写 JSX 时,我们会隐式地进行实例化,比如:

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

That bit of JSX gets translated into this by the compiler:

这一段 JSX 被编译器翻译成:

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

so in a sense, we are causing an element to be created because our code will call React.createElement(). But in another sense we aren’t, because it’s up to React to instantiate our component and then call render() for us. It’s simplest to consider React declarive. We describe what we want, and React figures out how to make it happen.

所以在某种意义上,我们造成一个元素被创建,因为我们的代码将调用 React.createElement()。但是在另一个意义上,并不是这样,因为实际是 React 实例化了我们的组件,为我们调用 render() 。让 React 是声明式的是最简单的。我们描述我们想要什么,而由 React 计算出如何实现。

一个小型的,山寨的 React,我们叫它 Feact(A tiny, fake React called Feact)

Now with a little bit of background under our belt, let’s get started building our React clone. Since this clone is tiny and fake, we’ll give it the imaginative name “Feact”.

现在我们对背景有一点了解,让我们开始构建我们的 React 克隆版。这个克隆是非常小型的山寨的,我们给它一个有想象力的名字 Feact

Let’s pretend we want to create this tiny Feact app:

我们假装要创建这个小巧的 Feact 应用程序:

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

For starters, let’s ditch the JSX. Assuming Feact was fully implemented, after running the JSX through the compiler we’d end up with

对于初学者,让我们来看看 JSX。假设 Feact 已经完全实现了 JSX,在通过编译器运行后,我们最终会得到:

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

JSX is a large topic on its own and a bit of a distraction. So from here on out, we will use Feact.createElement instead of JSX, so let’s go ahead and implement it

JSX 本身就是一个很大内容,有点脱离本文主题。所以从这里开始,我们将使用 Feact.createElement 代替 JSX,让我们继续实现它:

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

if (children) {
element.props.children = children;
}

return element;
}
};

Elements are just simple objects representing something we want rendered.

元素只是我们想要渲染的东西的简单对象表示。

Feact.render() 应该做什么?(What should Feact.render() do?)

Our call to Feact.render() passes in what we want rendered and where it should go. This is the starting point of any Feact app. For our first attempt, let’s define render() to look something like this

我们调用 Feact.render() ,传递了我们想要渲染的内容,以及它的位置。这是任何 Feact 应用的入口。对于我们的第一次尝试,让我们定义这样的 render()

const Feact = {
createElement() { /* as before */ },

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

When render() finishes, we have a finished webpage. So based on that, we know FeactDOMComponent is truly digging in and creating DOM for us. Let’s go ahead and take a stab at implementing it:

render() 完成时,我们会有一个完整的网页。基于此,我们知道 FeactDOMComponent 参与并为我们创建了 DOM。让我们继续努力实施它:

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 stores the DOM element it creates in this._hostNode. We don’t need that in part one, but we will in part three.

mountComponent 存储它创建的 DOM元素到 this._hostNode。在本文我们不需要它,但是在第三部分的文章我们需要。

在线示例:fiddle

In about 40 lines of pretty crappy code we’ve got an incredibly limited and pathetic little “React clone”! Feact isn’t going to take over the world, but it’s serving as a nice learning sandbox.

在这大约 40 行的相当糟糕的代码,我们得到了一个令人难以置信的非常的小的“React 克隆”。Feact 不会接管世界,但它是一个很好的学习沙箱。

添加用户定义的组件(Adding user defined components)

We want to be able to render more than just a single, hardcoded, DOM element. So let’s add support for defining component classes:

我们希望能够渲染不仅仅是一个单一的,硬编码的 DOM 元素,我们来添加对自定义组件类的支持:

const Feact = {
createClass(spec) {
function Constructor(props) {
this.props = props;
}

Constructor.prototype.render = spec.render;

return Constructor;
},

render(element, container) {
// our previous implementation can't
// handle user defined components,
// so we need to rethink this method
}
};

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')
);

Remember, we’re not dealing with JSX for this blog post series, because we’ve got plenty to deal with already. If we had JSX available, the above would look like

请记住,我们这系列文章并不处理 JSX,因为我们已经有很多要处理的内容了。如果我们有可用的 JSX 处理器,上面将会是被编译成这样:

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

We passed the component class into createElement. An element can either represent a primitive DOM element, or it can represent a composite component. The distinction is easy, if type is a string, the element is a native primitive. If it is a function, the element represents a composite component.

我们将组件类传递给 createElement。元素可以表示原生 DOM 元素,也可以表示复合组件。区别很容易,如果 type 是一个字符串,则元素表示原始 DOM 元素。如果它是一个函数,则该元素表示一个复合组件。

改进 Feact.render()(Improving Feact.render())

If you trace back through the code so far, you will see that Feact.render() as it stands now can’t handle composite components, so let’s fix that:

如果你回溯到目前为止的代码,你会看到现在的 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);
}
}

By giving users the ability to define their own components, Feact can now create dynamic DOM nodes that can change depending on the value of the props. There’s a lot going on in this upgrade to Feact, but if you trace through it, it’s not too bad. You can see where we call componentInstance.render(), to get our hands on an element that we can then pass into FeactDOMComponent.

通过给用户定义自己的组件的能力,Feact 现在可以创建能够根据 props 的值改变的动态 DOM 节点。在升级 Feact 的过程中有很多事情要做,但是如果你跟踪它的进行过程,这并不是很困难。你可以看到我们在哪里调用 componentInstance.render() ,以获得一个可以传入 FeactDOMComponent 的元素。

Notice how FeactCompositeComponentWrapper is directly creating a FeactDOMComponent? That’s a tight coupling which isn’t so great. We’ll fix this later. If React was this tightly coupled, it’d only ever be able to build web apps. Keeping ReactCompositeComponentWrapper in the dark about other component types surely made building React Native easier.

注意 FeactCompositeComponentWrapper 如何直接创建一个 FeactDOMComponent ?这个耦合很紧密的,但问题不大。我们稍后会解决这个问题。如果 React 是紧密耦合的,那么它只能构建 Web 应用程序。保持 ReactCompositeComponentWrapper 与其他组件类型透明会使本地化的 React(React Native) 更容易。

复合组件的改进(An improvement for composite components)

Currently our composite components must return elements that represent primitive DOM nodes, we can’t return other composite component elements. Let’s fix that. We want to be able to do this

目前,我们的复合组件必须返回原始 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);
}
}
}

This composite component’s render() is either going to return a primitive element or a composite component element. Currently Feact can’t handle this, if asTitle was true, FeactCompositeComponentWrapper would give FeactDOMComponent a non-native element, and FeactDOMComponent would blow up. Let’s fix FeactCompositeComponentWrapper

该复合元素的 render() 方法返回一个原始元素或者复合元素。当前版本的 Feact 无法处理它,如果 asTitle 值为 trueFeactCompositeComponentWrapper 将会传递给 FeactDOMComponent 一个非原始的元素,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);
}
}

Heads up, this “fix” is a short cut that’s just good enough to meet our current needs. Notice how it repeatedly calls render until it gets down to a primitive element? That’s not good enough, because those subcomponents need to participate in the entire lifecycle. For example, if we had support for componentWillMount, those subcomponents would never get their’s called. We’ll fix this later.

首先,这个“修复”是一个小的,足以满足我们当前的需求。注意,如果它重复调用 render,直到最终变成一个原始元素?这不够好,因为这些子组件需要参与整个生命周期。例如,如果我们已经支持了 componentWillMount` ,那么这些子组件的这个方法将永远不会被调用。我们稍后会解决这个问题。

再一次修复 Feact.render()(Fixing Feact.render() again)

The first version of Feact.render() could only handle primitive elements. Now it can only handle composite elements. It needs to be able to handle both. We could write a “factory” function that will create a component for us based on the element’s type, but there’s another approach that React took. Since FeactCompositeComponentWrapper components ultimately result in a FeactDOMComponent, let’s just take whatever element we were given and wrap it in such a way that we can just use a FeactCompositeComponentWrapper

第一个版本的 Feact.render() 只能处理原始元素,现在它只能处理复合元素。它需要能够处理两者。我们可以编写一个工厂函数,它将根据元素的类型为我们创建一个组件,但是还有另外一种 React 采用的方法。由于 FeactCompositeComponentWrapper 组件最终产生了 FeactDOMComponent,所以我们只需要使其接收我们给定的任何元素,并将它包装起来,以便我们可以只使用 FeactCompositeComponentWrapper

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);

// as before
}
};

ToplevelWrapper is basically a simple composite component. It could have been defined by calling Feact.createClass(). Its render method just returns the user provided element. Since ToplevelWrapper will get wrapped in a FeactCompositeComponentWrapper, we don’t care what type the user provided element was, FeactCompositeComponentWrapper will do the right thing regardless.

ToplevelWrapper 基本上是一个简单的复合组件。它可以通过调用 Feact.createClass() 来定义。它的渲染方法只返回用户提供的元素。由于ToplevelWrapper 将被包装在 FeactCompositeComponentWrapper 中,所以我们不关心用户提供的元素是什么类型的,FeactCompositeComponentWrapper 会做正确的事情。

第一部分总结(Conclusion to part one)

With that, Feact can render simple components. As far as basic rendering is concerned, we’ve hit most of the major considerations. In real React, rendering is much more complicated as there are many other things to consider such as events, focus, scroll position of the window, performance, and much more.

Here’s a final fiddle that wraps up all we’ve built so far:

现在,Feact 可以渲染简单的组件。就基本渲染而言,我们已经遇到了大多数需要主要考虑因素。在真实的 React 中,渲染要复杂得多,因为还有许多其他的事情需要考虑,比如事件,焦点,滚动窗口的位置,性能等等。

这是最后的 fiddle 示例,包装了我们迄今为止所建立的所有内容:

在线示例:fiddle

on to part two!
第二部分译文