精选资源:React.js 设计哲学
最后更新于 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) 的概念。
结合以上资料,可以总结出以下有关 React 的结论:
- 只是 UI 库,不是 MVC 框架
- 基于组件,而不是模板
- 响应式 UI,数据驱动视图,用 JS 对象作为 DOM 的轻量级表示,最小化更新 DOM,引入 reconciliation 概念
- DSL 语法,JSX
深入探索
深入探索 React 的架构设计和底层实现。
前置概念
框架/库一般会有部分前置概念(潜规则),看看 React 有哪些需要了解的东西。
JSX 是一种特定领域语言(DSL),极大的改善了我们的开发体验,上手很简单,已经成为很多前端框架的标配。
React 通过标签的首字母是否大写来识别我们声明的是原生 HTML 元素还是组件。
组件首字母为什么必须大写?从编译器视角(@babel/plugin-transform-react-jsx
)来理解:
React 有很多 APIs,可以从命名的角度理解这些 APIs 的实际作用。
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 的巨大优势,源于其核心算法(React Fiber)的实现。
一些前置知识:
异步渲染与渲染循环的实现:
更新流程的实现细节:
- Inside Fiber: in-depth overview of the new reconciliation algorithm in React
- In-depth explanation of state and props update in React
虚拟 DOM 与更新机制:
如何响应状态(state
)更新:
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 写法的支持:
- Introducing React Elements
- 新的术语,
ReactElement
替代 descriptor,ReactNode
替代 renderable - 术语集合(非官方)
v0.12
- 新的术语(
ReactElement
)和 APIs - 开源许可证由 Apache 2 变更为 BSD 3-Clause
key
和ref
从props
移到实例上- JSX 强制约定,所有小写字母或者包含破折号的标记都被解析为 html 标签,大写字母开头为组件
继发布官方应用数据管理工具 Flux
后,这次又发布了应该数据请求工具 Relay
,不同的是其底层基于 GraphQL 后端接口协议。
为了让 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
- 拆分为
react
和react-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
时隔多年,React 的官方文档进行了重构,以进一步降低学习门槛。
根据过去多年的维护经验,React 治理架构发生了重大变化,同时为了推动社区更快的了解新功能和第三方库的积极跟进,React Labs 决定定期向社区公布他们正在进行中的工作及其进展。