react的设计原则

我们写这个文档是为了让你更好的理解我们为什么觉定react该做什么以及不该做什么,我们的发展理念是什么样的。我们很高兴看到社区做出的贡献,但是我们不会去选择违反原则的贡献。

注意:本文档假定你对React有深入的了解。它描述了React本身的设计原则,而不是React组件或应用程序。

有关React的介绍,请查看Thinking in React

组合

React的关键特性是组件的组合。不同人写的组件应该要在一起很好的协同工作。对我们来说,重要的是你可以向组件添加功能,而不会影响整个代码库。

例如,应该可以将一些本地状态引入组件,而无需更改使用它的任何组件。同样,应该可以在必要时向任何组件添加一些初始化代码或移除一些代码。

在组件中使用状态或者生命周期方法没有“不好”。像任何强大的功能一样,它们应该适度使用,但我们没有打算删除它们。相反,我们认为它们是React有用的组成部分。我们可能会在未来启用更多功能模式,但本地状态和生命周期方法都将成为该模型的一部分。

组件通常被描述为“只是函数”,但在我们看来,它们需要的不仅仅是这些,需要更多的功能才能有用。在React中,组件描述了任何可组合的行为,这包括rendering, 生命周期和状态。一些外部库(如Relay)会增加组件的其他职责,例如描述数据依赖性。这些想法可能会以某种形式重新回到React。

常见的抽象

通常我们会拒绝添加一些可在用户端实现的功能。我们不想用一些没用的库得代码使你的应用程序臃肿。但是, 也有例外。

例如,如果React不提供对本地状态或生命周期方法的支持,则人们将为它们创建自定义抽象。当有多个抽象竞争时,React无法强制执行或利用其中任何一个的属性。他必须找最小的共同点去工作。

这就是为什么我们有时会为react添加功能的原因。如果我们注意到许多组件以不兼容或低效的方式实现某个功能,我们可能更愿意将其放到React中。我们不会轻易这样做。当我们这样做时,这是因为我们有信心提高抽象水平有益于整个生态系统。状态,生命周期方法,跨浏览器事件规范化就是很好的例子。

我们总是与社区讨论此类改进建议。可以通过React issue上的“蓝图”标签找到一些讨论。

出口

React是务实的。它是由facebook的一个产品的需求所驱动的。虽然它受到一些尚未完全成为主流的范式(如函数式编程)的影响,但是对于具有不同技能和经验水平的大多开发人员来说,这是一个明确的目标。

如果我们想要弃用我们不喜欢的模式,我们有责任去考虑它的所有现有用例,并在我们弃用它之前向社区宣传关于替代方案。如果某些对构建应用程序有用的模式难以以声明方式表达,我们将为其提供必要的API。如果我们无法找到一个完美的API,我们在许多应用程序中发现了必要的东西,我们将提供一个临时的subpar工作API,只要以后可以摆脱它并为未来的改进留下空间。

稳定性

我们重视API的稳定性。在facebook,我们使用的react中,有超过5万个组件。许多其他公司,包括TwitterAirbnb,也是React的重度用户。这就是我们通常不愿意改变公共API或行为的原因。

然而,我们认为“无变化”意义上的稳定被高估了。它很快就变成了停滞状态。相反,我们更喜欢稳定性,即“它在生产中被大量使用,当某些东西发生变化时,就会有一个明确的(最好是自动化的)迁移路径。”

当我们弃用某个模式时,我们会在Facebook上研究其内部使用情况并添加弃用警告。这可以让我们评估变化带来的影响。如果我们发现它太超前了,我们有时会放弃,我们需要更加战略性地考虑如何让代码库达到他们为这一变化做好准备的程度。

如果我们确信更改不会造成太大的破坏性并且迁移策略适用于所有用例,我们会向开源社区发布弃用警告。我们与Facebook之外的许多React用户保持密切联系,我们监控流行的开源项目并引导他们修复这些弃用。

鉴于Facebook React代码库的庞大规模,成功的内部迁移通常是其他公司也不会遇到问题的良好指标。然而,有时人们会指出我们没有想到的其他用例,我们为它们方法或重新考虑我们的方法。

没有充分理由,我们不会弃用任何东西。我们认识到,有时警告会引起挫败感,但我们会添加它们,因为可以为我们和社区中的许多人认为有价值的改进和新功能铺平道路。

例如,我们在React 15.2.0中添加了一个关于未知DOM props的警告。但是,修复此警告非常重要,以便我们可以向React引入对自定义属性的支持。我们添加的每个弃用都有这样的原因。

当我们添加弃用警告时,我们将其保留为当前主要版本的其余部分,并更改下一个主要版本中的行为。如果涉及大量重复的手工工作,我们会发布一个自动执行大部分更改的codemod脚本。Codemod使我们能够在没有停滞不前的情况下继续前进,我们也鼓励你使用它们。

你可以找到我们在react-codemod存储库中发布的codemod。

互通性

我们高度重视与现有系统的互操作性并逐步采用。 Facebook拥有庞大的非React代码库。它的网站使用了一个名为XHP的服务器端组件系统,React之前的内部UI库和React本身。对我们来说重要的是,任何产品团队都可以开始使用React作为一个小功能,而不是重写他们的代码来赌它。

这就是为什么React提供了逃避阴影以使用可变模型,并尝试与其他UI库一起很好地工作。可以将现有的命令式UI包装到声明性组件中,反之亦然。这对于逐步采用至关重要。

调度

即使你的组件被描述为函数,当你使用React时也不会直接调用它们。每个组件都返回需要呈现内容的描述,该描述可能包括用户编写的组件(如<LikeButton>)和特定于平台的组件(如<div>)。React在未来的某个时刻“展开”<LikeButton>,并且实际上根据组件的呈现结果递归地将更改应用于UI树。

这是一个微妙的区别,但却是一个强大的区别。由于你没有调用该组件函数但让React调用它,这意味着React有权在必要时延迟调用它。在其当前实现中,React以递归方式遍历树,并在单个tick中调用整个更新树的呈现函数。但是在将来它可能会开始延迟一些更新以避免丢帧

这是React设计中的一个共同主题。一些流行的库实现了“push”方法,其中在新数据可用时执行计算。然而,React坚持“pull”方法,在这种方法中计算可以延迟到必要时。

React不是通用数据处理库。它是一个用于构建用户界面的库。我们认为它在应用程序中具有独特的位置,可以知道哪些计算现在是相关的,哪些不是。

如果某些东西在屏幕外,我们可以延迟任何与之相关的逻辑。如果数据的到达速度快于帧速率,我们可以合并和批量更新。我们可以优先考虑来自用户交互(例如由按钮点击引起的动画)的工作,而不是重要的背景工作(例如渲染刚刚从网络加载的新内容)以避免丢帧。

要明确的是,我们现在没有利用这一点。(笔者:本文时间较长,已经在16版本实现了fiber和解)。然而,做这样的事情的自由是我们更喜欢控制调度的原因,以及为什么setState()是异步的。从概念上讲,我们将其视为“安排更新”。

如果我们让用户直接使用功能反应式编程的某些变体中常见的基于“推送”的范例来构建视图,那么对调度的控制将更难获得。我们想拥有“胶性”代码。

React的一个关键目标是在返回到React之前执行的用户代码量是最小的。这可以确保React保留根据其对UI的了解来调度和分割工作块的功能。

在团队中有一个内部笑话,React应该被称为“Schedule”,因为React不想完全“反应”。

开发体验

提供良好的开发者体验对我们很重要。

例如,我们维护React DevTools,它允许你检查Chrome和Firefox中的React组件树。我们听说它为Facebook工程师和社区带来了巨大的生产力提升。

我们还试图加倍努力,提供有用的开发人员警告。例如,如果你以浏览器不理解的方式嵌套标记,或者你在API中出现常见错误,则React会在开发过程中向你发出警告。开发者的提示以及其他的相关的检查就是为什么开发版的React比生产模式慢的主要原因。

我们在Facebook内部看到的使用模式有助于我们了解常见错误,以及如何尽早预防错误。当我们添加新功能时,我们会尝试预测常见错误并对其进行警告。我们一直在寻找改善开发人员体验的方法。我们很乐意听取你的建议并接受你的贡献,以使其更好。、

调试

当出现问题时,重要的是你有一个面包屑来跟踪代码库中的错误来源。在React中,props和state就是那些面包屑。

如果在屏幕上看到错误,可以打开React DevTools,找到负责渲染的组件,然后查看props和state是否正确。如果是,你知道问题出在组件的render函数中,或者是render调用的某个函数。问题是隔离的。

如果state错误,你知道问题是由此文件中的某个setState调用引起的。这也是定位和修复相对简单的原因,因为通常在单个文件中只有几个setState调用。

如果props是错误的,你可以在检查员中遍历树,寻找通过向下传递坏的props的组件。

这种跟踪任何UI到以当前props和state形式生成它的数据的能力对React来说非常重要。明确的设计目标是状态不会被“封闭”在闭包和组合器中,并且可以直接用于React。

虽然UI是动态的,但我们相信props和state的同步render函数将调试从猜测转变为枯燥但有限的过程。我们希望在React中保留这个约束,即使它使一些用例(如复杂的动画)更难。

配置

我们发现全局运行时配置选项存在问题。

例如,偶尔会要求我们实现React.configure(options)React.register(component)之类的函数。然而,这会带来多个问题,我们并不知道它们有很好的解决方案。

如果有人从第三方组件库调用这样的函数怎么办?如果一个React应用程序嵌入了另一个React应用程序,并且它们所需的配置不兼容怎么办?第三方组件如何指定它需要特定配置?我们认为全局配置在组合方面效果不佳。由于组合是React的核心,因此我们不在代码中提供全局配置。

但是,我们在构建级别上提供了一些全局配置。例如,我们提供单独的开发和生产构建。我们也可能在将来添加一个分析构建,我们可以考虑其他构建标志。

DOM以外

我们看到React的价值在于它允许我们编写具有更少错误并组合在一起的组件。DOM是React的原始渲染目标,但React Native对Facebook和社区同样重要。

与渲染器无关是React的一个重要设计约束。它在内部表示中增加了一些开销。另一方面,对核心的任何改进都可以跨平台进行转换。

拥有单一的编程模型可以让我们围绕产品而不是平台组建工程团队。到目前为止,这种权衡对我们来说是值得的。

履行

我们尽可能提供优雅的API。我们更不关心优雅的实施。现实中远非完美,并且在合理的范围内我们更愿意将丑陋的代码放入库中,如果这意味着用户不必编写它。当我们评估新代码时,我们正在寻找一个正确,高效的实现,并提供良好的开发人员体验。优雅是次要的。

我们更喜欢无聊的代码到聪明的代码。代码是一次性的,经常变化。因此,除非绝对必要,否则不引入新的内部抽象很重要。易于移动,更改和删除的详细代码优于过时抽象且难以更改的优雅代码。

工具优化

一些常用的API具有冗长的名称。例如,我们使用componentDidMount而不是didMountonMount。这是故意的。目标是使与库的互动点高度可见。

在像Facebook这样庞大的代码库中,能够搜索特定API的使用非常重要。我们重视不同的详细名称,特别是对于应该谨慎使用的功能。例如,在代码审查中很难错过dangerouslySetInnerHTML

优化搜索也很重要,因为我们依赖于codemod来进行重大更改。我们希望在代码库中应用大量自动更改变得简单而安全,并且独特的详细名称可帮助我们实现这一目标。同样,使用独特的名称可以轻松编写有关使用React的自定义lint规则,而无需担心潜在的误报。

JSX扮演着类似的角色。虽然React不需要它,但出于审美和实用的原因,我们在Facebook上广泛使用它。

在我们的代码库中,JSX为他们处理React元素树的工具提供了明确的提示。这使得可以添加构建时优化,例如提升常量元素,安全lint和codemod内部组件使用,并将JSX源位置包含在警告中

内部测试(吃狗粮)

我们尽力解决社区提出的问题。但是,我们可能会优先考虑人们在Facebook内部遇到的问题。也许与直觉相反,我们认为这是社区可以选择React的主要原因。

繁重的内部使用使我们相信React明天不会消失。 React是在Facebook创建的,以解决其问题。它为公司带来了实实在在的商业价值,并在其许多产品中得到应用。DogFooding意味着我们的愿景保持敏锐,我们有一个专注的方向前进。

这并不意味着我们忽略了社区提出的问题。例如,我们将Web组件SVG的支持添加到React,即使我们内部不依赖于它们中的任何一个。我们正在积极倾听你的痛点,并尽最大努力解决这些问题。社区使React成为我们的特别之处,我们很荣幸能够回馈。

在Facebook上发布了许多开源项目之后,我们了解到,试图让每个人都感到高兴同时产生的焦点不集中的项目效果不佳。相反,我们发现选择一小部分观众并专注于让他们快乐带来积极的净效果。正是我们对React做的,到目前为止解决Facebook产品团队遇到的问题已经很好地转化为开源社区。

这种方法的缺点是,有时我们没有足够关注Facebook团队不必处理的事情,例如“入门”体验。我们非常清楚这一点,我们正在考虑如何以一种有益于社区中每个人的方式进行改进,而不会犯下我们之前对开源项目所犯的错误。

原文:Design Principles

Last updated