精选资源: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