Error Handle in React 16

July 26, 2017 by


随着React 16发布在即,我们打算介绍一些在组件内部React如何处理JavaScript错误。这些改变包含在React 16的beta版本中,并将成为React 16的一部分。

顺便一提,你可以尝试我们刚发布了React 16的第一个测试版本!

React 15及之前的行为

过去,组件内部的JavaScript异常曾经常阻断了React内部状态并导致其在下一次渲染时触发了隐藏的错误。这些错误常常是由应用程序代码中的早期错误所引起的,但React并未提供一种在组件里优雅处理的方式,也不会从异常中回复。

错误边界介绍

UI部分的JavaScript异常不应阻断整个应用。为了为React用户解决这一问题,React 16引入了“错误边界(error boundary”)”这一新概念。

错误边界作为React组件,用以捕获在子组件树种任何地方的JavaScript异常,打印这些错误,并展示备用UI而非让组件树崩溃。错误边界会捕获渲染期间,在生命周期方法中以及在其整个树的构造函数中的异常。

若定义一个称为componentDidCatch(error, info)的新生命周期方法,则类组件将成为错误边界:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

而后可作为一个正常组件进行使用:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

componentDidCatch()方法的作用类似于JavaScript的catch {},但仅针对组件。仅有类组件可以成为错误边界。实际上,大多数时间你会想仅声明一次错误边界组件并在整个应用中使用。

注意,错误边界仅可以捕获树中后代的组件错误。一个错误边界无法捕获其自身的错误。若错误边界尝试渲染错误信息失败,则该错误会传递至上方最接近的错误边界。而这也类似JavaScript中的catch {}块的工作方式。

Live Demo

查看在React 16测试版关于如何声明和使用错误边界的例子

放置错误边界

错误边界的粒度完全取决于你。你可能将其包装在顶层路由组件中并为用户展示“内部异常(Something went wrong)”的信息,类似于服务端框架处理崩溃。你可能也会在错误边界包装一些内部组件用以保护不会让应用的余下部分不会崩溃。

未捕获错误的新行为

这一改变有一个重要的意义。作为React 16中不是由错误边界引起的错误将会使得整个React组件树被卸载。

我们曾争论这一决定,但在我们的经验中,将损坏的UI留在那里要比完全移除它要糟糕得多。例如,在类似Messenger这样的产品中留下可见的损坏的UI可能会导致一些人将信息发送给错误的人。类似地,对于支付应用来说显示错误的金额要比什么都不显示糟糕得多。

这一改变意味着随着迁移至React 16,你们将会发现之前未留意过的应用程序存在的崩溃。增加错误边界能够让你在发生异常时提供更好的用户体验。

例如,Facebook Messenger将边栏,信息面板,会话日志以及消息输入的内容包装到单独的错误边界中。若其中某一个组件的UI崩溃了,其余的仍能正常交互。

我们也鼓励你使用JS错误报告服务(或自己构建)以让你能够了解在产品中产生的未处理的异常,并修复它们。

组件栈追踪

React 16会打印所有在开发环节中发生在渲染过程的错误到控制台,即使应用程序意外地将他们吞了。除了错误信息和JavaScript堆栈,其还提供了组件栈追踪。闲杂你可以在组件树中精确地查看错误产生的地方:

Component stack traces in error message

你也可以在组件堆栈中查看文件名和行数。这一功能在Create React App 项目中默认开启:

Component stack traces with line numbers in error message

若你不使用Create React App,你可以手动添加该插件 到你的Babel配置中。注意其仅能在开发环境中使用并禁止在生产环境中使用。

为何不使用try / catch

try / catch 很好但其仅适用于命令式的代码:

try {
  showButton();
} catch (error) {
  // ...
}

然而,React组件是声明式的,并指定了什么应该被渲染:

<Button />

错误边界保留了React声明式的特性,同时其行为和你期望的一直。例如,即使一个在componentDidUpdate周期由组件树内部底层的setState导致的错误,其仍能够正确地传递到最近的错误边界。

自React 15开始的命名变更

React 15 在不同的方法名下为错误边界包含了一个非常有限的支持:unstable_handleError。该方法不再生效,同时自React 16第一个测试版本发布开始,你需要在你的代码中将其修改为componentDidCatch

为应对这一改变,我们已提供了一份 codemod以用于自动地迁移你的代码。

React v15.6.0

June 13, 2017 by


今天我们发布了React 15.6.0。由于我们在准备React 16.0,因此我们修复和清理了许多东西。此次发布将继续为后续铺路。

改进Inputs

在React 15.6.0,输入框的onChange事件稍微可靠了点并能够处理更多边界条件,涵盖以下:

  • 当单选按钮被点击但未改变时事件未触发(issue 1471
  • 用方向键(arrow keys)修改范围(range)类型的输入框(issue 554
  • 在IE 11复制文本到文本框中(issue 7211
  • 在IE 11自动填充(issue 6614
  • 在IE 11中通过'x'键或右键'删除'清空输入框(issue 6822
  • 在IE 11中渲染输入框中已存在字符时事件未触发(issue 2185

感谢Jason Quense以及其他帮助解决上述问题和提出PR的各位。

更少嘈杂的弃用警告

我们还为之后的弃用引入了一系列新的警告。这些并不会影响大多数用户,更多细节可以查看下面的修改日志。

自上次发布之后,我们从社区得到了一些有价值的反馈,弃用警告会产生噪音并使得测试失败。在React 15.6,我们采用console.warn而不是console.error来对弃用警告进行降级处理。我们的其他警告将仍使用console.error因为他们面对的可能是引发异常的棘手问题。不同于我们其他警告,弃用警告可以延期修复并且不会在发布环节对你的引用造成问题。我们认为降低弃用警告的紧急程度将会让你在下个版本的升级中更为容易。感谢参与这一问题讨论的各位。


安装

我们推荐使用Yarnnpm来管理前端依赖。若你是初次接触包管理器,Yarn documentation是一个不错的起点。

通过Yarn安装React,运行:

yarn add react@^15.6.0 react-dom@^15.6.0

通过npm安装React,运行:

npm install --save react@^15.6.0 react-dom@^15.6.0

我们推荐如webpackBrowserify打包器,以让你可以编写模块化代码并将其打包到一起放在一个小的包内以优化加载时间。

记住默认情况下,React在开发模式下运行会带有额外检查并提供有用的警告。当部署你的应用时,确保其在生产模式编译

万一你未使用打包器,我们也提供了一个预先构建好的包在npm上,你可以在你的页面中通过script标签引入

我们还在npm上发布了reactreact-dom及附件包的15.5.0版本以及在bower上的react包。


修改日志

15.6.0 (June 13, 2017)

React

  • 采用console.warn而不是console.error来降级处理弃用警告。(@flarnie#9753 的提交)
  • React.createClass增加弃用警告。将用户指向create-react-class。(@flarnie#9771 的提交)
  • 增加弃用警告并为React.DOM工厂辅助方法分离为新模块。(@nhunzaker#8356 的提交)
  • React.createMixin的弃用警告辅助方法将不再使用。(@aweary#8853 的提交)

React DOM

  • style属性中增加CSS变量支持。 (@aweary#9302 的提交)
  • 增加CSS网格(Grid)样式属性支持。(@ericsakmar#9185 的提交)
  • 修复输入框改变可变值类型异常。(@nhunzaker#9806 的提交)
  • 修复在某些输入场景下onChange未触发的问题。(@jquense#8575 的提交)
  • 修复控制数字输入框错误允许期间的异常。(@nhunzaker#9584 的提交)
  • 修复性能记录(performance entry)被清除的bug。(@chrisui#9451 的提交)

React v15.5.0

April 7, 2017 by Andrew Clark


自上次更新React已经是一年前了。我们下一次的重要发布,React 16,将包含一些让人兴奋的提升,包含一个完全重写的React内核。我们认真考虑了稳定性,并致力于以最小的改动将这些特性带给我们的用户。

最后,我们发布了React 15.5.0。

新的弃用警告

最大的改变是我们将React.PropTypesReact.createClass 移到他们的自己的包。二者仍然可以通过主要的React独享访问,但是在开发模式下,使用其中的一个将在控制台上打印一个过时的弃用警告。这将会让之后代码大小有所优化。

这些警告并不会影响你的应用行为。然而,我们意识到他们可能会造成一些困惑,尤其是你使用的测试框架将console.error视为失败时。

添加新警告并不是我们轻易做的。 警告在React中不仅仅是建议 - 它们是我们能够让尽可能多地人使用最新版React的策略的组成部分。我们从不在不增加路径的情况下增加警告。

因此,当警告可能在短期内造成困惑,我们相信能刺激开发者在当前迁移代码库而防止在未来产生更大困惑。提前修复警告确保你已为下一个重要发布做好了准备。若你的应用在15.5时产生了0个警告,那么在16中应该能够继续工作而无需任何改变。

关于每一项新的弃用,我们已经提供了一份代码修改(codemod)以自动迁移你的代码。它们已可以利用来作为 react-codemod项目的部分。

从React.PropTypes迁移

属性类型是用于在开发期间对属性进行运行时校验的一项特性。我们已将内建的属性类型提取到一个独立的包中以反映是否并非每个人都会使用的事实。

在15.5中,不再是从React主对象中访问 PropTypes ,而是安装prop-types包并从中引入他们:

// Before (15.4 and below)
import React from 'react';

class Component extends React.Component {
  render() {
    return <div>{this.props.text}</div>;
  }
}

Component.propTypes = {
  text: React.PropTypes.string.isRequired,
}

// After (15.5)
import React from 'react';
import PropTypes from 'prop-types';

class Component extends React.Component {
  render() {
    return <div>{this.props.text}</div>;
  }
}

Component.propTypes = {
  text: PropTypes.string.isRequired,
};

对于这一改变,codemo 自动处理这一转换。基础用法:

jscodeshift -t react-codemod/transforms/React-PropTypes-to-prop-types.js <path>

propTypescontextTypeschildContextTypes仍将和以前一样。唯一的改变是以前内建的的验证器现在位于独立的包内。

你也可能考虑使用 Flow来静态地检查你的JavaScript代码类型,包括React 组件.

从React.crateClass迁移

当React首次发布时,在JavaScript中并没有通用的方式创建类,因此我们提供我们一个自定义方法:React.createClass

而后,类被添加进该语言中作为ES2015的一部分,因此我们增加了使用JavaScript的类来创建React组件的能力。与函数式组件一起,JavaScript类现在都是在React创建组件的首选方式

对于已经存在的 createClass 组件,我们建议你将它们迁移到JavaScript类。然而,若你的组件依赖于混合(mixins),迁移至类可能不会立即可行。若是如此,create-react-class 作为一个顺带的替换方案已在npm上可用:

// Before (15.4 and below)
var React = require('react');

var Component = React.createClass({
  mixins: [MixinA],
  render() {
    return <Child />;
  }
});

// After (15.5)
var React = require('react');
var createReactClass = require('create-react-class');

var Component = createReactClass({
  mixins: [MixinA],
  render() {
    return <Child />;
  }
});

你的组件仍然能够和之前一样继续工作。

对于这一变化,codemo 尝试转换createClass组件为一个JavaScript类,并在必要时回退到create-react-class。该方案已处理了Facebook内部数千计的组件。

基本用法:

jscodeshift -t react-codemod/transforms/class.js path/to/components

不再支持的React附件

我们将停止维护React附件包。真实地,大多数包已经有很长时间没有维护了。它们仍将持续继续工作,但我们建议尽快迁移以防止将来的破坏。

  • react-addons-create-fragment - React 16将优先支持碎片,在这点上该包已不再必要。我们推荐使用键控元素的数组。
  • react-addons-css-transition-group。使用react-transition-group/CSSTransitionGroup。1.1.1版本提供了插入式的替代方案。
  • react-addons-linked-state-mixin 直接设置valueonChange处理器
  • react-addons-pure-render-mixin - 使用React.PureComponent
  • react-addons-shallow-compare - 使用React.PureComponent
  • react-addons-transition-group 使用react-transition-group/TransitionGroup。1.1.1版本提供了一个插入式的替代方案。
  • react-addons-update - 使用immutability-helper,一个插入式替代方案。
  • react-linked-input - 直接设置valueonChange处理器。

我们也放弃对于react-with-addonsUMD构建的支持。其将在React 16中被移除。

React测试套件

当前,React 测试套件位于react-addons-test-utils内。在15.5中,我们将其从包中独立出去,并将它们迁移至react-dom/test-utils

// Before (15.4 and below)
import TestUtils from 'react-addons-test-utils';

// After (15.5)
import TestUtils from 'react-dom/test-utils';

这反应了我们所说的测试套件实际上是一套包装了DOM渲染器的API。

浅渲染是一个例外,其并不是DOM规范。浅渲染器已被移至react-test-renderer/shallow

// Before (15.4 and below)
import { createRenderer } from 'react-addons-test-utils';

// After (15.5)
import { createRenderer } from 'react-test-renderer/shallow';

感谢

特别感谢这些人转让npm包名称的所有权:


安装

我们推荐使用Yarnnpm来管理前端依赖。若你是初次接触包管理器,Yarn documentation是一个不错的起点。

通过Yarn安装React,运行:

yarn add react@^15.5.0 react-dom@^15.5.0

通过npm安装React,运行:

npm install --save react@^15.5.0 react-dom@^15.5.0

我们推荐如webpackBrowserify打包器,以让你可以编写模块化代码并将其打包到一起放在一个小的包内以优化加载时间。

记住默认情况下,React在开发模式下运行会带有额外检查并提供有用的警告。当部署你的应用时,确保其在生产模式编译

万一你未使用打包器,我们也提供了一个预先构建好的包在npm上,你可以在你的页面中通过script标签引入

我们还在npm上发布了reactreact-dom及附件包的15.5.0版本以及在bower上的react包。


修改日志

15.5.0 (April 7, 2017)

React

  • React.createClass增加一个独立的警告。将用户指向create-react-class。(@acdlited9a4fa4的提交)
  • React.PropTypes增加一个独立的警告。将用户指向prop-types。(@acdlite043845c的提交)
  • 修复同时使用ReactDOMReactDOMServer。(@wacii#9005提的PR)
  • 修复关于Closure Compiler的问题。(@Shastel#8882提的PR)
  • 另一个关于Closure Compiler的修复。(@Shastel#8882提的PR)
  • 为无效的元素类型警告增加组件栈信息。(@n3tr#8495提的PR)

React DOM

  • 修正当在数字输入中回退时的Chrome bug。(@nhunzaker#7359提的PR)
  • 增加react-dom/test-utils,暴露React测试套件。(@bvaughn)

React Test Renderer

  • 修复子组件不会调用componentWillUnmount。(@gre 在 [#8512](https://github.com/facebook/react/pull/8512]提的PR)
  • 增加 react-test-renderer/shallow,暴露浅渲染器。

React Addons

  • 最后一次发布附件;他们将不再被维护
  • 移除peerDependencies以让附件能够无限期继续工作。(@acdlite@bvaughn8a06cd767a8db3的提交)
  • 更新以移除React.createClassReact.PropTypes 的引用。(@acdlite12a96b9的提交)
  • react-addons-test-utils被移除。使用react-dom/test-utilsreact-test-renderer/shallow 代替。(@bvaughn

React v15.4.0

November 16, 2016 by Dan Abramov


Today we are releasing React 15.4.0.

We didn't announce the previous minor releases on the blog because most of the changes were bug fixes. However, 15.4.0 is a special release, and we would like to highlight a few notable changes in it.

Separating React and React DOM

More than a year ago, we started separating React and React DOM into separate packages. We deprecated React.render() in favor of ReactDOM.render() in React 0.14, and removed DOM-specific APIs from React completely in React 15. However, the React DOM implementation still secretly lived inside the React package.

In React 15.4.0, we are finally moving React DOM implementation to the React DOM package. The React package will now contain only the renderer-agnostic code such as React.Component and React.createElement().

This solves a few long-standing issues, such as errors when you import React DOM in the same file as the snapshot testing renderer.

If you only use the official and documented React APIs you won't need to change anything in your application.

However, there is a possibility that you imported private APIs from react/lib/*, or that a package you rely on might use them. We would like to remind you that this was never supported, and that your apps should not rely on internal APIs. The React internals will keep changing as we work to make React better.

Another thing to watch out for is that React DOM Server is now about the same size as React DOM since it contains its own copy of the React reconciler. We don't recommend using React DOM Server on the client in most cases.

Profiling Components with Chrome Timeline

You can now visualize React components in the Chrome Timeline. This lets you see which components exactly get mounted, updated, and unmounted, how much time they take relative to each other.

React components in Chrome timeline

To use it:

  1. Load your app with ?react_perf in the query string (for example, http://localhost:3000/?react_perf).

  2. Open the Chrome DevTools Timeline tab and press Record.

  3. Perform the actions you want to profile. Don't record more than 20 seconds or Chrome might hang.

  4. Stop recording.

  5. React events will be grouped under the User Timing label.

Note that the numbers are relative so components will render faster in production. Still, this should help you realize when unrelated UI gets updated by mistake, and how deep and how often your UI updates occur.

Currently Chrome, Edge, and IE are the only browsers supporting this feature, but we use the standard User Timing API so we expect more browsers to add support for it.

Mocking Refs for Snapshot Testing

If you're using Jest snapshot testing, you might have had issues with components that rely on refs. With React 15.4.0, we introduce a way to provide mock refs to the test renderer. For example, consider this component using a ref in componentDidMount:

import React from 'react';

export default class MyInput extends React.Component {
  componentDidMount() {
    this.input.focus();
  }

  render() {
    return (
      <input
        ref={node => this.input = node}
      />
    );
  }
}

With snapshot renderer, this.input will be null because there is no DOM. React 15.4.0 lets us avoid such crashes by passing a custom createNodeMock function to the snapshot renderer in an options argument:

import React from 'react';
import MyInput from './MyInput';
import renderer from 'react-test-renderer';

function createNodeMock(element) {
  if (element.type === 'input') {
    return {
      focus() {},
    };
  }
  return null;
}

it('renders correctly', () => {
  const options = {createNodeMock};
  const tree = renderer.create(<MyInput />, options);
  expect(tree).toMatchSnapshot();
});

We would like to thank Brandon Dail for implementing this feature.

You can learn more about snapshot testing in this Jest blog post.


Installation

We recommend using Yarn or npm for managing front-end dependencies. If you're new to package managers, the Yarn documentation is a good place to get started.

To install React with Yarn, run:

yarn add react@15.4.0 react-dom@15.4.0

To install React with npm, run:

npm install --save react@15.4.0 react-dom@15.4.0

We recommend using a bundler like webpack or Browserify so you can write modular code and bundle it together into small packages to optimize load time.

Remember that by default, React runs extra checks and provides helpful warnings in development mode. When deploying your app, make sure to compile it in production mode.

In case you don't use a bundler, we also provide pre-built bundles in the npm packages which you can include as script tags on your page:

We've also published version 15.4.0 of the react, react-dom, and addons packages on npm and the react package on bower.


Changelog

React

  • React package and browser build no longer "secretly" includes React DOM.
    (@sebmarkbage in #7164 and #7168)
  • Required PropTypes now fail with specific messages for null and undefined.
    (@chenglou in #7291)
  • Improved development performance by freezing children instead of copying.
    (@keyanzhang in #7455)

React DOM

  • Fixed occasional test failures when React DOM is used together with shallow renderer.
    (@goatslacker in #8097)
  • Added a warning for invalid aria- attributes.
    (@jessebeach in #7744)
  • Added a warning for using autofocus rather than autoFocus.
    (@hkal in #7694)
  • Removed an unnecessary warning about polyfilling String.prototype.split.
    (@nhunzaker in #7629)
  • Clarified the warning about not calling PropTypes manually.
    (@jedwards1211 in #7777)
  • The unstable batchedUpdates API now passes the wrapped function's return value through.
    (@bgnorlov in #7444)
  • Fixed a bug with updating text in IE 8.
    (@mnpenner in #7832)

React Perf

  • When ReactPerf is started, you can now view the relative time spent in components as a chart in Chrome Timeline.
    (@gaearon in #7549)

React Test Utils

  • If you call Simulate.click() on a <input disabled onClick={foo} /> then foo will get called whereas it didn't before.
    (@nhunzaker in #7642)

React Test Renderer

  • Due to packaging changes, it no longer crashes when imported together with React DOM in the same file.
    (@sebmarkbage in #7164 and #7168)
  • ReactTestRenderer.create() now accepts {createNodeMock: element => mock} as an optional argument so you can mock refs with snapshot testing.
    (@Aweary in #7649 and #8261)

Our First 50,000 Stars

September 28, 2016 by Vjeux


Just three and a half years ago we open sourced a little JavaScript library called React. The journey since that day has been incredibly exciting.

Commemorative T-Shirt

In order to celebrate 50,000 GitHub stars, Maggie Appleton from egghead.io has designed us a special T-shirt, which will be available for purchase from Teespring only for a week through Thursday, October 6. Maggie also wrote a blog post showing all the different concepts she came up with before settling on the final design.

React 50k Tshirt

The T-shirts are super soft using American Apparel's tri-blend fabric; we also have kids and toddler T-shirts and baby onesies available.

Proceeds from the shirts will be donated to CODE2040, a nonprofit that creates access, awareness, and opportunities in technology for underrepresented minorities with a specific focus on Black and Latinx talent.

Archeology

We've spent a lot of time trying to explain the concepts behind React and the problems it attempts to solve, but we haven't talked much about how React evolved before being open sourced. This milestone seemed like as good a time as any to dig through the earliest commits and share some of the more important moments and fun facts.

The story begins in our ads org, where we were building incredibly sophisticated client side web apps using an internal MVC framework called BoltJS. Here's a sample of what some Bolt code looked like:

var CarView = require('javelin/core').createClass({
  name: 'CarView',
  extend: require('container').Container,
  properties: {
    wheels: 4,
  },
  declare: function() {
    return {
      childViews: [
        { content: 'I have ' },
        { ref: 'wheelView' },
        { content: ' wheels' }
      ]
    };
  },
  setWheels: function(wheels) {
    this.findRef('wheelView').setContent(wheels);
  },
  getWheels: function() {
    return this.findRef('wheelView').getContent();
  },
});

var car = new CarView();
car.setWheels(3);
car.placeIn(document.body);
//<div>
//  <div>I have </div>
//  <div>3</div>
//  <div> wheels</div>
//</div>

Bolt introduced a number of APIs and features that would eventually make their way into React including render, createClass, and refs. Bolt introduced the concept of refs as a way to create references to nodes that can be used imperatively. This was relevant for legacy interoperability and incremental adoption, and while React would eventually strive to be a lot more functional, refs proved to be a very useful way to break out of the functional paradigm when the need arose.

But as our applications grew more and more sophisticated, our Bolt codebases got pretty complicated. Recognizing some of the framework's shortcomings, Jordan Walke started experimenting with a side-project called FaxJS. His goal was to solve many of the same problems as Bolt, but in a very different way. This is actually where most of React's fundamentals were born, including props, state, re-evaluating large portions of the tree to “diff” the UI, server-side rendering, and a basic concept of components.

TestProject.PersonDisplayer = {
  structure : function() {
    return Div({
      classSet: { personDisplayerContainer: true },
      titleDiv: Div({
        classSet: { personNameTitle: true },
        content: this.props.name
      }),
      nestedAgeDiv: Div({
        content: 'Interests: ' + this.props.interests
      })
    });
  }
};

FBolt is Born

Through his FaxJS experiment, Jordan became convinced that functional APIs — which discouraged mutation — offered a better, more scalable way to build user interfaces. He imported his library into Facebook's codebase in March of 2012 and renamed it “FBolt”, signifying an extension of Bolt where components are written in a functional programming style. Or maybe “FBolt” was a nod to FaxJS – he didn't tell us! ;)

The interoperability between FBolt and Bolt allowed us to experiment with replacing just one component at a time with more functional component APIs. We could test the waters of this new functional paradigm, without having to go all in. We started with the components that were clearly best expressed functionally and then we would later continue to push the boundaries of what we could express as functions.

Realizing that FBolt wouldn't be a great name for the library when used on its own, Jordan Walke and Tom Occhino decided on a new name: “React.” After Tom sent out the diff to rename everything to React, Jordan commented:

Jordan Walke: I might add for the sake of discussion, that many systems advertise some kind of reactivity, but they usually require that you set up some kind of point-to-point listeners and won't work on structured data. This API reacts to any state or property changes, and works with data of any form (as deeply structured as the graph itself) so I think the name is fitting.

Most of Tom's other commits at the time were on the first version of GraphiQL, a project which was recently open sourced.

Adding JSX

Since about 2010 Facebook has been using an extension of PHP called XHP, which enables engineers to create UIs using XML literals right inside their PHP code. It was first introduced to help prevent XSS holes but ended up being an excellent way to structure applications with custom components.

final class :a:post extends :x:element {
  attribute :a;
  protected function render(): XHPRoot {
    $anchor = <a>{$this->getChildren()}</a>;
    $form = (
      <form
        method="post"
        action={$this->:href}
        target={$this->:target}
        class="postLink"
      >{$anchor}</form>
    );
    $this->transferAllAttributes($anchor);
    return $form;
  }
}

Before Jordan's work had even made its way into the Facebook codebase, Adam Hupp implemented an XHP-like concept for JavaScript, written in Haskell. This system enabled you to write the following inside a JavaScript file:

function :example:hello(attrib, children) {
  return (
    <div class="special">
      <h1>Hello, World!</h1>
      {children}
    </div>
  );
}

It would compile the above into the following normal ES3-compatible JavaScript:

function jsx_example_hello(attrib, children) {
  return (
    S.create("div", {"class": "special"}, [
      S.create("h1", {}, ["Hello, World!"]),
      children
    ]
  );
}

In this prototype, S.create would immediately create and return a DOM node. Most of the conversations on this prototype revolved around the performance characteristics of innerHTML versus creating DOM nodes directly. At the time, it would have been less than ideal to push developers universally in the direction of creating DOM nodes directly since it did not perform as well, especially in Firefox and IE. Facebook's then-CTO Bret Taylor chimed in on the discussion at the time in favor of innerHTML over document.createElement:

Bret Taylor: If you are not convinced about innerHTML, here is a small microbenchmark. It is about the same in Chrome. innerHTML is about 30% faster in the latest version of Firefox (much more in previous versions), and about 90% faster in IE8.

This work was eventually abandoned but was revived after React made its way into the codebase. Jordan sidelined the previous performance discussions by introducing the concept of a “Virtual DOM,” though its eventual name didn't exist yet.

Jordan Walke: For the first step, I propose that we do the easiest, yet most general transformation possible. My suggestion is to simply map xml expressions to function call expressions.

  • <x></x> becomes x( )
  • <x height=12></x> becomes x( {height:12} )
  • <x> <y> </y> </x> becomes x({ childList: [y( )] })

At this point, JSX doesn't need to know about React - it's just a convenient way to write function calls. Coincidentally, React's primary abstraction is the function. Okay maybe it's not so coincidental ;)

Adam made a very insightful comment, which is now the default way we write lists in React with JSX.

Adam Hupp: I think we should just treat arrays of elements as a frag. This is useful for constructs like:

<ul>{foo.map(function(i) { return <li>{i.data}</li>; })}</ul>

In this case the ul(..) will get a childList with a single child, which is itself a list.

React didn't end up using Adam's implementation directly. Instead, we created JSX by forking js-xml-literal, a side project by XHP creator Marcel Laverdet. JSX took its name from js-xml-literal, which Jordan modified to just be syntactic sugar for deeply nested function calls.

API Churn

During the first year of React, internal adoption was growing quickly but there was quite a lot of churn in the component APIs and naming conventions:

  • project was renamed to declare then to structure and finally to render.
  • Componentize was renamed to createComponent and finally to createClass.

As the project was about to be open sourced, Lee Byron sat down with Jordan Walke, Tom Occhino and Sebastian Markbåge in order to refactor, reimplement, and rename one of React's most beloved features – the lifecycle API. Lee came up with a well-designed API that is still in place today.

  • Concepts
    • component - a ReactComponent instance
    • state - internal state to a component
    • props - external state to a component
    • markup - the stringy HTML-ish stuff components generate
    • DOM - the document and elements within the document
  • Actions
    • mount - to put a component into a DOM
    • initialize - to prepare a component for rendering
    • update - a transition of state (and props) resulting a render.
    • render - a side-effect-free process to get the representation (markup) of a component.
    • validate - make assertions about something created and provided
    • destroy - opposite of initialize
  • Operands
    • create - make a new thing
    • get - get an existing thing
    • set - merge into existing
    • replace - replace existing
    • receive - respond to new data
    • force - skip checks to do action
  • Notifications
    • shouldObjectAction
    • objectWillAction
    • objectDidAction

Instagram

In 2012, Instagram got acquired by Facebook. Pete Hunt, who was working on Facebook photos and videos at the time, joined their newly formed web team. He wanted to build their website completely in React, which was in stark contrast with the incremental adoption model that had been used at Facebook.

To make this happen, React had to be decoupled from Facebook's infrastructure, since Instagram didn't use any of it. This project acted as a forcing function to do the work needed to open source React. In the process, Pete also discovered and promoted a little project called Webpack. He also implemented the renderToString primitive which was needed to do server-side rendering.

As we started to prepare for the open source launch, Maykel Loomans, a designer on Instagram, made a mock of what the website could look like. The header ended up defining the visual identity of React: its logo and the electric blue color!

React website mock

In its earliest days, React benefited tremendously from feedback, ideas, and technical contributions of early adopters and collaborators all over the company. While it might look like an overnight success in hindsight, the story of React is actually a great example of how new ideas often need to go through several rounds of refinement, iteration, and course correction over a long period of time before reaching their full potential.

React's approach to building user interfaces with functional programming principles has changed the way we do things in just a few short years. It goes without saying, but React would be nothing without the amazing open source community that's built up around it!