就在三年半前,我们开源了一个名为 React 的小型 JavaScript 库。自那时以来,这段旅程令人难以置信地激动人心。

纪念 T 恤

为了庆祝在 GitHub 上获得 50,000 个 star,来自 egghead.io 的 Maggie Appleton 为我们设计了一款特别的 T 恤,该 T 恤将于 仅一周 内,截止至 10 月 6 日星期四,在 Teespring 上发售。Maggie 还写了一篇博文,展示了她在确定最终设计之前提出的所有不同的概念。

React 50k Tshirt

这些 T 恤非常柔软,采用 American Apparel 的三混合面料;我们还有儿童和幼儿 T 恤以及婴儿连体衣。

衬衫的收益将捐赠给 CODE2040,这是一个非营利组织,致力于为代表性不足的少数族裔(尤其关注黑人和拉丁裔人才)提供技术领域的参与、认知和机会。

考古学

我们花了很多时间试图解释 React 背后的概念以及它试图解决的问题,但我们很少谈论 React 在开源之前是如何演变的。这个里程碑似乎是挖掘最早的提交并分享一些更重要的时刻和有趣的事实的好时机。

故事始于我们的广告部门,我们在那里使用一个名为 BoltJS 的内部 MVC 框架构建了非常复杂的客户端 Web 应用程序。以下是一些 Bolt 代码的示例:

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 引入了许多最终进入 React 的 API 和特性,包括 rendercreateClassrefs。Bolt 引入了 refs 的概念,作为创建可以命令式使用的节点引用的方式。这与遗留的互操作性和增量采用相关,虽然 React 最终会努力变得更加函数式,但 refs 证明是当需要时跳出函数式范式的一种非常有用的方式。

但是,随着我们的应用程序变得越来越复杂,我们的 Bolt 代码库变得非常复杂。Jordan Walke 意识到该框架的一些缺点,开始尝试一个名为 FaxJS 的辅助项目。他的目标是解决与 Bolt 相同的问题,但方式截然不同。这实际上是 React 的大部分基础知识的诞生地,包括 props、state、重新评估树的大部分以“diff” UI、服务器端渲染以及组件的基本概念。

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 诞生

通过他的 FaxJS 实验,Jordan 坚信函数式 API(不鼓励突变)提供了一种更好、更可扩展的方式来构建用户界面。他在 2012 年 3 月将他的库导入到 Facebook 的代码库中,并将其重命名为“FBolt”,表示 Bolt 的扩展,其中组件以函数式编程风格编写。或者也许“FBolt”是对 FaxJS 的致敬——他没有告诉我们!;)

FBolt 和 Bolt 之间的互操作性使我们能够尝试仅用更函数式的组件 API 替换一个组件。我们可以测试这种新的函数式范式的效果,而无需全力以赴。我们从那些明显最适合以函数式表达的组件开始,然后我们将继续推动我们可以表达为函数的边界。

Jordan Walke 和 Tom Occhino 意识到 FBolt 在单独使用时不是该库的一个好名字,因此他们决定采用一个新名称:“React”。在 Tom 发送将所有内容重命名为 React 的 diff 之后,Jordan 评论说:

Jordan Walke:我可能会补充一点,为了讨论起见,许多系统都宣传某种反应性,但它们通常需要您设置某种点对点侦听器,并且不适用于结构化数据。此 API 响应任何状态或属性更改,并且适用于任何形式的数据(与图本身一样深入结构化),因此我认为该名称很合适。

当时 Tom 的其他大部分提交都是在 GraphiQL 的第一个版本上进行的,该项目最近已开源。

添加 JSX

自 2010 年左右以来,Facebook 一直在使用 PHP 的扩展,称为 XHP,它使工程师能够使用 XML 字面量直接在他们的 PHP 代码中创建 UI。它首先被引入以帮助防止 XSS 漏洞,但最终成为使用自定义组件构建应用程序的绝佳方式。

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

在 Jordan 的工作甚至进入 Facebook 代码库之前,Adam Hupp 为 JavaScript 实现了一个类似 XHP 的概念,用 Haskell 编写。该系统使您可以在 JavaScript 文件中编写以下内容:

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

它会将以上内容编译为以下正常的 ES3 兼容 JavaScript:

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

在这个原型中,S.create 会立即创建并返回一个 DOM 节点。关于这个原型的大部分讨论都围绕着 innerHTML 与直接创建 DOM 节点的性能特征。当时,普遍地推动开发人员朝着直接创建 DOM 节点的方向发展并不理想,因为它表现不佳,尤其是在 Firefox 和 IE 中。Facebook 当时的 CTO Bret Taylor 在当时的讨论中赞成 innerHTML 而不是 document.createElement

Bret Taylor:如果您不相信 innerHTML,这是一个小型微基准测试。它在 Chrome 中大致相同。innerHTML 在最新版本的 Firefox 中快大约 30%(在以前的版本中更多),在 IE8 中快大约 90%。

这项工作最终被放弃,但在 React 进入代码库后被恢复。Jordan 通过引入“Virtual DOM”的概念搁置了之前的性能讨论,尽管它的最终名称尚不存在。

Jordan Walke:对于第一步,我建议我们尽可能做到最简单但最通用的转换。我的建议是简单地将 xml 表达式映射到函数调用表达式。

  • `` 变为 x( )
  • `` 变为 x( {height:12} )
  • `` 变为 x({ childList: [y( )] })

目前,JSX 不需要了解 React - 它只是一种编写函数调用的便捷方式。巧合的是,React 的主要抽象是函数。好吧,也许不是那么巧合;)

Adam 提出了一个非常有见地的评论,现在是我们使用 JSX 在 React 中编写列表的默认方式。

Adam Hupp:我认为我们应该将元素数组视为一个片段。这对于以下结构很有用:

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

在这种情况下,ul(..) 将获得一个 childList,其中只有一个子项,它本身就是一个列表。

React 最终没有直接使用 Adam 的实现。相反,我们通过 fork js-xml-literal 创建了 JSX,这是 XHP 创建者 Marcel Laverdet 的一个辅助项目。JSX 的名称取自 js-xml-literal,Jordan 对其进行了修改,使其仅成为深度嵌套函数调用的语法糖。

API 动荡

在 React 的第一年,内部采用迅速增长,但组件 API 和命名约定发生了相当多的变化:

在该项目即将开源时,Lee Byron 与 Jordan Walke、Tom Occhino 和 Sebastian Markbåge 坐下来,重构、重新实现并重命名 React 最受欢迎的功能之一——生命周期 API。Lee 提出了一个设计良好的 API,至今仍然有效。

Instagram

2012 年,Instagram 被 Facebook 收购。当时在 Facebook 照片和视频部门工作的 Pete Hunt 加入了他们新成立的 Web 团队。他想完全使用 React 构建他们的网站,这与 Facebook 使用的增量采用模型形成鲜明对比。

为了实现这一目标,React 必须与 Facebook 的基础设施分离,因为 Instagram 没有使用其中的任何一个。该项目充当了强制执行开源 React 所需工作的推动力。在此过程中,Pete 还发现并推广了一个名为 webpack 的小型项目。他还实现了 renderToString 原语,这是进行服务器端渲染所必需的。

当我们开始准备开源发布时,Instagram 的设计师 Maykel Loomans 制作了该网站可能看起来像什么的模拟图。标头最终定义了 React 的视觉标识:其徽标和电蓝色!

React website mock

在早期,React 受益于公司内部早期采用者和合作者的反馈、想法和技术贡献。虽然事后看来似乎是一夜成名,但 React 的故事实际上是一个很好的例子,说明新想法通常需要经过多轮改进、迭代和纠正才能充分发挥其潜力。

React 使用函数式编程原则构建用户界面的方法在短短几年内改变了我们的做事方式。毋庸置疑,如果没有围绕它建立起来的令人惊叹的开源社区,React 将一无是处!