代码库概述
本节将概要介绍React 代码库的组织,约定已经实现。
如果您想参与React ,我们希望本指南可以帮助您更轻松地进行更改。
我们不一定在React应用程序中推荐任何这些约定。其中许多因历史原因而存在,可能会随着时间而变化。
外部依赖
React 几乎没有外部依赖。通常,一个require()
指出React 自己代码库中的一个文件。然而, 也有一些相对罕见的例外。
fbjs repository 存在是因为React 共享一些性 Relay 库的工具,我们保持它们同步。我们不依赖NOde 生态系统的相同的小模块,因为我们想要Facebook 工程师必要时可以修改它们。fbjs 中没有工具被认为是公共的API,它们只是在Facebook 项目中例如React 中使用。
顶级目录
克隆完React repository 之后,你将看到几个顶级文件夹:
packages
包含了 React 仓库中所有包中的元数据(例如package.json
)和源代码(src
子目录)。 如果您想更改相关代码,则每个包的src
子目录是您应该重点花时间的地方。fixtures
包含一些针对贡献者的小型React 测试应用程序。build
是 React 构建输出。它不再仓库中但是当你第一次构建 它,在你的React 副本中将会显示。
文档托管在 React 的独立存储库中。
还有一些其它的顶级文件夹,但是它们通常用于工具,当你贡献代码时,通常不会遇到它们。
共同测试
我们还没有一个顶级目录作为单元测试。相反,我们把它们放到相对测试的文件的__tests__
目录。
例如,一个setInnerHTML.js
测试位于__tests__/setInnerHTML-test.js
的旁边。
警告 和 Invariants
React 代码库使用waring
模块去显示警告:
var warning = require('warning');
warning(
2 + 2 === 4,
'Math is not working today.'
);
当 warning
条件为 false
时显示警告。
考虑一种方式,条件应该响应正常环境而不是异常环境。
这是一个好主意去避免控制台中重复的警告的垃圾信息。
var warning = require('warning');
var didWarnAboutMath = false;
if (!didWarnAboutMath) {
warning(
2 + 2 === 4,
'Math is not working today.'
);
didWarnAboutMath = true;
}
警告只在开发环境下有效。在生产环境,它们完全被跳过。如果你需要禁止某些代码路径执行,请使用invariant
模块:
var invariant = require('invariant');
invariant(
2 + 2 === 4,
'You shall not pass!'
);
当 invariant
条件为 false
时显示 invariant。
“不变(invariant)”只是“这个条件总保持为真”的一种说法。你可以认为它是一个明确肯定(assertion)。
保持在开发和生产行为相似是重要的,因为invariant
在开发和生产都抛出。错误信息在生产中自动替换为错误代码,以避免字节大小的负面影响。
开发 和 生产
你可以在代码库中使用__DEV__
伪全局变量来包括只在开发环境中的代码块。
在编译步骤中它是内联的(inlined),在CommonJS 构建中,它转成process.env.NODE_ENV !== 'production'
去检测。
对于独立的构建,它在unminified 的构建中变成true
,完全跳过在minified 构建中它保护的if
块。
if (__DEV__) {
// This code will only run in development.
}
Flow
我们最近开始引入Flow 检测代码库。使用@flow
注释在许可头部的注解的文件正在被检测。
我们接受推送请求adding Flow annotations to existing code。Flow 注解看上去像这样:
ReactRef.detachRefs = function(
instance: ReactInstance,
element: ReactElement | string | number | null | false,
): void {
// ...
}
尽可能的,新的代码应该使用Flow 注解。你可以在本地使用 yarn flow
去检测你的代码。
动态注入
在一些模块中React 使用动态注入。虽然它总是明确的,但它仍然是不幸的,因为它阻碍了对代码的理解。它存在的主要原因是React 最初只支持DOM 作为目标。React Native 开始是作为React 的分支。我们必须加入动态注入来让React Native 覆盖一些行为。
你可以看到模块声明它们的动态依赖关系:
// Dynamically injected
var textComponentClass = null;
// Relies on dynamically injected value
function createInstanceForText(text) {
return new textComponentClass(text);
}
var ReactHostComponent = {
createInstanceForText,
// Provides an opportunity for dynamic injection
injection: {
injectTextComponentClass: function(componentClass) {
textComponentClass = componentClass;
},
},
};
module.exports = ReactHostComponent;
injection
域不是被任何特定的方式处理。但是按照管理,这意味着这个模块系统啊在运行时有一些(大概是平台特定的(platform-specific))依赖注入。
在代码库中有多个注入点。在未来,我们打算摆脱动态注入机制并且在构建时静态地绑定所有的块。
多个包
React 是一个monorepo。它的仓库包括多个独立的包,使得它们的改变可以协调在一起,并且文档和问题都位于一个位置。
React 核心
React 的“核心(core)”包括所有的顶级React
API,例如:
React.createElement()
React.Component
React.Children
React core 只包含定义组件所需的 API 。 它不需要包含 reconciliation 索然或任何平台特定的(platform-specific)代码。它被用于React DOM 和React Native components。
React core 代码位于源代码树的src/isomorphic
。它是npm 作为react
包是合适的。相应的独立的浏览器构建被称为 react.js
并且它导出一个全局React
。
渲染器
React 最初是为了DOM 创建的但是最后它也改变为通过 React Native 支持本地平台。接下来介绍React 内部的“渲染(renderers)”概念。
渲染器可以将React树变成底层的平台调用。
Renderers 位于src/renderers:
- React DOM Renderer 渲染React component 为DOM。它实现了top-level
ReactDOM
APIs 并且作为react-dom npm 包是有效的。它也可以被用作独立的浏览器插件react-dom.js
并且导出一个全局ReactDOM
。 - React Native Renderer 渲染React component 为本地视图(native view)。它被用作React Native 内部通过react-native-renderer npm 包。在未来它的一份复制可能被检入(get checked into)到React Native 代码库,导致React Native 可以在它自己的空间更新React。
- React Test Renderer 渲染React component 为JSON 树。它被Jest 的Snapshot Testing 特性所用,并且作为react-test-renderer npm 包使用。
这唯一的其他官方支持的渲染器是react-art。为了避免我们在改变React 时破环它,我们将它作为src/renderers/art 检入(check in)并运行它的测试套件。尽管如此,它的GitHub repository 仍然作为真理源(the source of truth)。
注意:
技术上native渲染器是一个非常薄的层,教导React 同React 实现交互。真正的平台特定的(platform-specific)代码管理本地视图位于React Native repository和它的component 在一起。
Reconcilers
即使像React DOM 和React Native 在渲染器上非常不同,仍然共享许多逻辑。尤其是,reconciliation 算法应该尽可能的相似,以至于像声明式渲染,自定义component,state,lifecycel methods,和refs 仍然是跨平台一致。
为了解决它,不同的渲染器在它们之间共享一些代码。我们成React 的这部份为“reconciler”。当一个更新例如setState
被调度,这个reconciler 调用书中的component 上的render()
去加载、更新、或卸载它们。
Reconcilear 没有独立为包,因为它们目前还没有公共的API。相反,它们专门被用作渲染器例如React DOM 和React Native。
Stack Reconciler
“堆栈”协调程序是React 15及更早版本的实现。 我们已经停止使用它,但在 next section 中有详细记录。
Fiber Reconciler
“fiber”reconciler 是一个新的努力去解决stack reconciler 中固有的问题和一些长期存在的问题。
它完全重写了这个reconciler,当前处于in active development。
它的主要目标是:
- 能够分块可中断工作的能力
- 能够优先、复位和重用工作进程中的能力
- 在父节点和子节点之间来回支持布局在React 中的能力
- 从
render()
中返回多个element 的能力 - 更好支持错误边界
你可以在React Fiber Architecture 中了解更多。目前,它仍然是试验性的,距离和stack reconciler 特性等价是遥远的。
它的源代码位于src/renderers/shared/fiber
Event System
React 实现了一个合成的事件系统,它是渲染器不可知的,在React DOM 和React Native 中工作。它的源代码位于src/renderers/shared/shared/event
。
这有一个video with a deep code dive into it 60 分钟。
What Next?
阅读 下一节 去了解关于当前reconciler 实现的更多细节。