跳到主要内容

精选资源:React.js 设计哲学

· 阅读需 24 分钟
不如怀念
Web 前端工程师 (Web Front-end Engineer)

最后更新于 2024-04-21 14:31:00

精选资源

这是一个系列,收集不同领域相关的精选(高价值)内容,包括深入分析文章、视频、工具等。

探索一项新兴技术出现的背景、动机,尤其是其背后的设计哲学,更甚的是在不断的版本演进过程中遇到了什么问题,产生了什么思考,以及是如何决策并得到最优解。

React.js 发展至今已经成为 Web 前端领域一个重要的存在,这里主要是收集官方和非官方途径发布的一些非常有价值的资料,探索其背后的设计哲学,了解其团队决策时的思考和权衡。

定义 React.js

React.js 作为一个 UI 库,官方强调了其三大特点:

  • 声明式编程(Declarative)
  • 基于组件(Component-Based)
  • 可重用,跨平台(Learn Once, Write Anywhere)

它背后所蕴含的出色的工程化思想是其大受欢迎的重要因素。

声明式编程

声明式编程是对多个编程范式的总称,一般用来做对比的就是命令式编程。对于命令式编程,一个比较典型的例子就是使用原生 JS(或 jQuery)调用相应 API 一步一步操作 DOM 的过程;而声明式编程,则忽略具体的过程(控制流),只描述逻辑,一个典型的例子就是使用 SQL 语句查询数据库的过程。

声明式编程

对于声明式编程所带来的优势和一些显著的缺点,推荐阅读以下文章:

组件化开发

React 作为一个 UI 库,其最大的特点就是引入了虚拟 DOM(Virtual DOM,VDOM) 的概念。

虚拟 DOM

React.js 的技术验证原型项目:

一些其它虚拟 DOM 实现:

结合以上资料,可以总结出以下有关 React 的结论:

  • 只是 UI 库,不是 MVC 框架
  • 基于组件,而不是模板
  • 响应式 UI,数据驱动视图,用 JS 对象作为 DOM 的轻量级表示,最小化更新 DOM,引入 reconciliation 概念
  • DSL 语法,JSX

深入探索

深入探索 React 的架构设计和底层实现。

前置概念

框架/库一般会有部分前置概念(潜规则),看看 React 有哪些需要了解的东西。

JSX 是一种特定领域语言(DSL),极大的改善了我们的开发体验,上手很简单,已经成为很多前端框架的标配。

React 通过标签的首字母是否大写来识别我们声明的是原生 HTML 元素还是组件。

React 组件

组件首字母为什么必须大写?从编译器视角(@babel/plugin-transform-react-jsx)来理解:

React 有很多 APIs,可以从命名的角度理解这些 APIs 的实际作用。

API 命名

React 早期的一些关于 API 命名的提案讨论:

核心概念

必须了解的 React 核心概念。

元素(Element)是 React 中描述 UI 的基本单位,而组件(Component)是对元素的封装。

React 中有两个关键概念,即属性(props)和状态(state)。

要在应用全局共享数据,涉及到上下文(Context)概念。

为了保证应用程序的稳定性,React 提出了错误边界(Error Boundaries)的方案,允许 UI 在局部奔溃,但不会影响整个应用。

错误边界

随着 React Hooks API 的出现,函数组件逐渐取代了类组件,也取代了高阶组件(HOC)模式

React 的更新是自顶向下的,并不是真正的精准更新,所以即使是只有父元素发生变化,所有的子元素也会重新渲染

更新逻辑

属性(props)变更会导致重新渲染,重新渲染不一定意味着属性(props)发生变更:

要知道为何 React 拥有如此高的性能,需要了解其视图更新机制的一些设计细节。

利用 React 提供的一些能力,来解决部分性能问题。

性能优化

通过组合模式:

代码分割,延迟加载:

非紧急更新:

React v18 提供了新的渲染机制,即并发模式(concurrent mode),其目标是支持许多新的 APIs。

React 不仅支持服务端渲染(Server-side Render, SSR)技术,现在还支持服务端组件(React Server Component, RSC)

技术细节

下面是一些底层的技术细节剖析。

在开始之前,值得了解一下逆向工程的信息。

先从简单的小细节开始。

实现小细节

React 支持类和函数两种组件,但如何区分它们?

$$typeof 属性的意义?

为什么要写 super(props),但是不写也行?

React 在开发模式下有很多友好的提示,会不会影响生产环境的性能?

要理解 React 的设计和实现,就必须了解其理论模型。

基本理论模型

高性能是 React 的巨大优势,源于其核心算法(React Fiber)的实现。

React hooks 为函数组件带来了维护状态的可能性,了解一下其实现细节。

随着 React hooks 的推出,类组件在被逐渐取代,两种组件有什么差异呢?

深入理解服务端渲染(Server-side Render, SSR) 技术。

以上是针对一些关键点的内容,社区内还提供了一些针对 React 源码分析的系列资源。

迭代轨迹

React 一些重要的版本更新节点。

v0.4.0

要点
  • 引入 key 属性让开发者可以手动干预 reconciliation 过程
  • 实现符合 W3C 规范的改进合成事件(synthetic event) 系统
  • 组件更新实现批处理,优化渲染性能
  • JSX 支持节点注释

除过使用 React.js 来构建 Web 应用的开发者以外,对于开发和维护一个工具库的开发者们来说也有值得关注的内容,例如 React 对 Dom 元素的标记从 id 属性迁移到 data-reactid,以实现与第三方库的友好兼容,以及官方后续发布了 Chrome 调试工具,大大提升了开发者体验(DX)。

这个时期,React 的服务端渲染能力受到社区的关注,但还处于初级阶段。

v0.9 RC

要点
  • CDN 提供一个未压缩开发版本(包含警告信息) 和一个压缩生产版本
  • 引入 shouldComponentUpdate() API,让开发者可以手动优化性能
  • key 发生更改时,组件重新挂载
  • 缩短 data-reactid 以提高性能
  • (v0.9)引入测试工具 ReactTestUtils

经过几个版本的迭代,官方一方面增强了对原生 DOM 的兼容和支持,另一方面也逐步废弃了一些抽象的 APIs,例如对于表单元素获取值的 getValue() API 被废弃,转而推荐开发者使用原生 DOM 的 .value 属性,这是 React 很重要的设计理念,尽可能少的新概念,尽可能多的贴近原生开发体验。

此外,性能一直是官方团队关注的重点,一些小细节,例如对于 data-reactid 属性值的缩短,生产包中用数字替代具体的文本等等,都是值得工具库开发者们进行思考的。

v0.11

要点
  • 支持返回 null
  • JSX 的命名空间
  • 介绍 Descriptors

在 v0.10.0 发布后,官方团队就已经思考发布 v1.0.0 的事情了,这也为 React 迎来了一系列重大变更。

针对单向数据流架构,官方介绍了一种应用数据管理方案 Flux

JSX 作为一种特定领域语言(DSL),实际上不仅仅被 React.js 所使用,考虑到其是对原生 Dom 的抽象表示并且应用广泛,官方决定将其独立为一个标准规范。

为了进一步提升开发者体验(DX),准备推出对 ES6 Class 写法的支持:

React 元素

v0.12

要点
  • 新的术语(ReactElement)和 APIs
  • 开源许可证由 Apache 2 变更为 BSD 3-Clause
  • keyrefprops 移到实例上
  • JSX 强制约定,所有小写字母或者包含破折号的标记都被解析为 html 标签,大写字母开头为组件

继发布官方应用数据管理工具 Flux 后,这次又发布了应该数据请求工具 Relay,不同的是其底层基于 GraphQL 后端接口协议。

数据请求方案

为了让 React 保持足够简单,对 React 元素进行了约束,计划引入了一定程度的优化。

简化 React 元素
  • Streamlining React Elements
    • 不可变的 props
    • 删除所有者(owner)语义,上下文(context)更改为基于父级而不是基于所有者
    • 编译器优化

v0.13

要点
  • 正式发布 ES6 Class 写法的支持,类中不会内置 this 的自动绑定
  • 不可变的 props
  • 生命周期方法中调用 setState 现在均是异步批量处理
  • setState 允许传入函数访问 props 和上一个 state

从一开始,React 的目标就不仅仅是 Web 平台,现在官方开源了原生移动平台(Android/iOS)的方案。

原生移动平台

React 可以和第三方库友好的兼容,而且在页面上可以存在多个根节点,如果做到这些需要了解 React 的 render() API 和其它相关的顶级 APIs。

v0.14

要点
  • 拆分为 reactreact-dom 两个包
  • 引入无状态的函数组件概念
  • 编译器优化

React 被拆分为多个包意味着跨平台渲染的可能性,这也正是社区正在尝试的。另一方面,官方视图让 React 保持简单,并让开发者能够建立一个正确的心智模型,所以对一些核心概念进行了解释。

作为对 React 稳定性的承诺,官方团队决定采用新的版本控制方案,后续的版本发布升级为主要版本(major),即下个版本为 v15.0.0

v15.0

要点
  • 内部创建 DOM 元素由 innerHTML 迁移到 document.createElement,DOM 元素上不再需要 data-reactid 属性性能更好,同时对 SVG 提供完整支持
  • 函数组件也支持返回 null

在之前提到过,官方发布包时分为了开发包和生产包两种模式,其中就对错误信息做了特殊处理以优化生产环境的加载性能,此次又引入错误代码系统

React 推崇组件模型,编程模型类似于面向对象模式(OOP),长期以来针对如何共享组件逻辑,官方给出了组合的混入(mixin)两种方案,随着社区的发展,组合被认为是更成熟可靠且简单的方案,而混入方案不建议被采用。

组合比混入更好

为了降低入门的成本,官方推出了开发模板 create-react-app 项目。

React 应用的稳定性更进一步,官方计划引入错误边界概念,组件局部异常不会导致应用全局崩溃。

v16.0

要点
  • 组件允许返回字符串和片段
  • 错误边界机制正式发布
  • 引入 createPortal API,创建脱离组件树的组件
  • 服务端渲染优化,支持流式处理
  • 支持自定义 DOM 属性
  • 官方改为使用 Rollup 打包,产物更小
  • 更改为 MIT 许可证
  • 新的核心架构 Fiber

该版本为 React 引入了大量更新,也是一次里程碑式的更新,引入的新架构模型 Fiber 带来了异步渲染机制,应用性能再次得到提升。

如果涉及到维护工具库,推荐可以看看 React 项目基础设施的变化,值得参考学习。

新的异步渲染机制,带来了两个新的概念:时间切片(time slicing)挂起(suspense)

v16.3.0: New lifecycles and context API

要点
  • 发布官方 Context API,支持简单的状态管理场景
  • 新的 createRef() API,简化字符串 ref API 的复杂性
  • 新的 forwardRef API,支持跨层级转发 ref
  • 为了适应新的架构和异步渲染机制生命周期 APIs 发生变化
  • 引入 StrictMode 组件,方便发现潜在的问题

继发布 Chrome 调试工具扩展后,官方这一次推出了专门针对性能进行调试的工具。

v16.6.0: lazy, memo and contextType

要点
  • 引入 memo() API 以实现类似 PureComponent 的作用
  • 引入 lazy() API 配合 Suspense 实现代码分割
  • 类组件添加官方 Context API 支持

该版本是对 v16.3.0 的延续。

v16.8: The One With Hooks

要点
  • 发布 hooks APIs

自 v16.0 发布以来,大的更新一直不断,React 一直追求的是开发 Web 应用应该足够简单,所以很明显的一个趋势是函数组件将逐步替代类组件成为主流,而 Hook APIs 的发布正式让函数组件具备了在大部分场景下直接替代类组件的可能。

v17.0

要点
  • 事件委托机制更改为挂载根组件的节点,事件系统保持和浏览器一致
  • 移除“事件池(event pooling)”优化
  • 新的 jsx 转换机制,不再需要显式导入 React
  • useEffect 的清理函数现在运行时机都是异步的,同步运行的替代方案为 useLayoutEffect
  • 引入“原生的组件堆栈(Native Component Stacks)”以更方便的调试

该主要版本的目标是为 React 未来的渐进式更新铺平道路,所以并没有重大的新功能发布。

距离上一个主要版本(v16.0)的发布已经过去大概 3 年的时间,React 的迭代速度已经逐渐的慢下来了,社区出现了很多认为 React 已经失去创新能力的声音,但事实是很多重要的功能已经探索了多年,之所以迟迟不发布是因为 React 团队在追求最优解,另一方面,React 的社区生态发展的速度依然很快。

下一个主要版本(v18.0)的重大更新聚焦于并发(Concurrent)渲染和服务端组件(Server-side Component),项目的治理架构也将迎来变化。

v18.0

要点
  • 新的并发(Concurrent)渲染器
  • 新增自动批处理机制,状态更新不再只是在事件处理程序中批处理
  • 新增 Transition 功能处理非紧急更新
  • 新的 Suspense API,支持服务器渲染
  • 新的客户端和服务器渲染 APIs
  • 新的 Hook API useId / useDeferredValue / useSyncExternalStore / useInsertionEffect

时隔多年,React 的官方文档进行了重构,以进一步降低学习门槛。

新官方网站

根据过去多年的维护经验,React 治理架构发生了重大变化,同时为了推动社区更快的了解新功能和第三方库的积极跟进,React Labs 决定定期向社区公布他们正在进行中的工作及其进展。