用 react 也有段时间了, 是时候看看人家源码了. 看源码之前看到官方文档 https://reactjs.org/docs/codebase-overview.html 有这么篇文章介绍其代码结构了, 为了看源码能顺利些, 遂决定将其翻译来看看, 小弟英语也是半瓢水, 好多单词得查词典, 不当之处请批评. 直接从字面翻译的, 后面看源码后可能会在再修改下.
下面是翻译
这部分将给你介绍下 react 代码的基本结构, 代码约定和它的基本实现.
如果你想为 react 贡献代码的话, 我们希望这篇指南能让你写代码更加舒服.
我们不推荐将这些约定用在 react 应用中, 因为这些约定大多是基于一些历史原因存在的, 随着时间推移可能会发生变化.
外部依赖
react 几乎没有外部依赖. 通常 require()指向的是 react 自己代码库的一个文件. 但是也有一些例外.
由于 react 想要通过库共享一些诸如 Relay 的小工具, 所以存在 fbjs repository, 而且我们让他们是同步的. 我们没有依赖任何 node 生态系统下的小模块, 因为我们希望 Facebook 的工程师的能能再任何必要的时候修改他们. fbjs 中的任何工具都不能被认为是公共 API, 并且他们只是为 Facebook 的一些工程使用, 比如 react.
一级目录
克隆了 react 的仓库后你会发现在里边有几个一级目录.
packages 目录包括一些元数据 (如 package.JSON) 和 react 库提供的所有包的源码(src 的下面), 如果你想修改代码, src 下面就是你要花时间最多的地方.
fixtures 目录包括了为贡献者准备的一些小的 react 的测试应用
build 是 react 打包输出的目录. 他不在代码库管理范畴, 但是当你第一次打包后就会生成.
文档是放在和 react 不同的另一个仓库管理的.
还有一些其他一级目录, 他们大多是工具层面的, 在你贡献代码时可能不会用到他们能.
共同测试(Colocated Tests)
我们没有搞个一级目录来做单元测试. 我们把它放在了被测试文件相邻的被称为__tests__的目录.
举个例子, 对于 setInnerHTML.JS 这个文件的测试被放在与他同级的__tests__/setInnerHTML-test.JS 这个里边.
这个词不知道怎么翻译
Warnings and Invariants
react 中使用 warning 模块显示警告信息.
- var warning = require('warning');
- warning(
- 2 + 2 === 4,
- 'Math is not working today.'
- );
当警告条件是 false 的时候会展示警告信息
可以这么理解, 条件应该指示正常的情况, 而不是异常的情况. 就是说第一个参数是 true 表示的是正常, false 是异常.
最好避免使用 console 取代warnings.
- 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!'
- );
当条件为 false 时, 这个方法会直接抛出异常.
"Invariant" 就是说这个条件为真, 你可以认为他就是做了个断言.
保持开发环境和生产环境一致是很重要的, 因此 invariant 在生产环境和开发环境都可以抛出异常. 生产环境下的错误消息被自动替换成错误码, 以防增加代码体积.
Development and Production
你可以使用__DEV__这个为全局变量指定仅仅在开发环境才执行的代码块.
他是在编译过程中工作的, 他是在 commonjs 编译的时候检查 process.env.NODE_ENV !== 'production'这个值.
单独编译的时候, 他在未压缩版是 true, 在压缩版直接被去掉了.
- if (__DEV__) {
- // 这里边的代码只会带开发环境执行
- }
- Flow
我们最近开始引入 flow 做静态类型检查, 在文件头的注释里标注了 @flow 的使用了类型检查.
我们接受在现有代码加入 flow类型检查的 pull request (不错哎, 可以试着提个 pull request 哦). Flow 的签名类似下面这样.
- ReactRef.detachRefs = function(
- instance: ReactInstance,
- element: ReactElement | string | number | null | false,
- ): void {
- // ...
- }
时机成熟的时候, 新代码要用 Flow 签名, 你可以在本地运行yarn flow 用 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;
注入的部分没有以任何方式特殊处理. 但是规定, 它的意思是这个模块想在运行时有一些依赖 (可能是平台特定的) 被注入进去.
代码里边有几个注入的入口. 未来, 我们将废弃掉这种动态植入的机制, 方案是在编译时以静态方式处理他们.
多包
react 是个 monorepo, 他的仓库包含了多个独立的包, 因此他们的修改可以合在一起, 而且 issues 也可以放在一个地方.
React 核心
react 的核心是所有顶级 API, 包括:
- React.createElement()
- React.Component
- React.Children
react 核心只包括定义组件必要的 API, 并不包括 reconciliation 算法和平台特定代码. React DOM 和 React Native 都使用了他们.
react 核心的相关代码在 packages/react 里边. NPM 使用时在 react 这个包里边, 浏览器版的是 react.JS, 他挂载一个被称为 React 的全局变量.
Renderers
react 起初是为 DOM 创造的, 但是后台通过 RN 被用来支持原生环境了. 这里介绍加 react 内部的 "renderers" 的理念.
"renderers" 管理了 react 树如何变成平台可调用的东西.
Renderers 也在 packages 里边
React DOM Renderer 把 react 组件渲染进 DOM. 他实现了顶级的 ReactDOM APIs, 在 react-dom 这个 NPM 包里被暴露出来. 浏览器版叫 react-dom.JS, 通过 ReactDOM 这个全局变量暴露出来.
React Native Renderer 把 react 组件渲染到原生视图层里. 他被 RN 内部使用.
React Test Renderer 把 react 组件渲染成JSON 树, 他被 Jest 的一个特性 Snapshot Testing 使用, 在 react-test-renderer 这个 NPM 包里可用.
另一个官方唯一支持的渲染器是 react-art, 他曾经是个独立的库, 现在被移进来了.
注意
技术上 react-native-renderer 是很薄的一层, 只是用来和 RN 的实现相互配合, 真正的平台相关代码是 RN库里一些 native view.
Reconcilers(协调器)
相当多的渲染器, 如 Reat DOM, React Native 需要共享一套逻辑. 尤其 reconciliation算法需要足够的相似, 以便让 rendering, 自定义组件, 状态, 生命周期函数和 refs 能跨平台工作.
为了解决这个问题, 不同的渲染器共用一些代码. 我们把 React 中的这个部分叫做 "reconciler". 当一个更新比如 setState 要执行了, Reconcilers 就去在组件上调用 render(), 然后 mounts, updates, 或者 unmounts 他们.
Reconcilers没有独立成包, 因为他现在还没有公共 API. 相反, 他仅仅是在渲染器被使用, 比如 React DOM , React Native.
Stack Reconciler
Stack Reconciler 是在 react15 之前实现使用的, 现在已经不用了, 但是下一部分的文档还会有详细的介绍.
Fiber Reconciler
"Fiber" 是为了解决 stack reconciler 固有问题和修复长期存在的 bug 所做的努力, 他从 react16 开始成为默认的 Reconciler.
他的主要目标是:
在 chunks 里分离可中断的工作
在过程中重建, 重用 work 或者改变他的优先级 (瞎翻译的) 的能力
在父子组件前进或回退以只是 react 中的布局的能力
在 render 方法里返回多个元素的能力
更好的支持错误边际
你可在这里和这里阅读更多关于 Fiber 架构的相关信息. 但是 React16 对他做了封装, 默认不支持异步特性了.
他的源码在 packages/react-reconciler 里边.
事件系统
react 实现了一个对 renders 透明的事件系统, 这个系统被用于 react dom 和 react native. 源码在 packages/events;
这里有个视频 https://www.youtube.com/watch?v=dRo_egw7tBc
来源: https://www.cnblogs.com/floor/p/10094323.html