React 19 前沿:从 Transition 到 Server Components

useTransition、Lanes 模型、RSC 全链路——Dan Abramov 2025 年最新输出精读

来源:react.dev/reference/react/useTransition

核心:非阻塞更新 + 优先级控制

const [isPending, startTransition] = useTransition()

function handleClick() {
  startTransition(() => {
    setTab(nextTab)  // 这个更新被标记为 transition(低优先级)
  })
}

1. Transition vs 普通 setState

普通 setState(SyncLane):
  - 同步执行,阻塞渲染
  - 用户交互立即响应
  - 适合:点击按钮、输入框

Transition(Transition Lane):
  - 可以被打断
  - 不阻塞用户交互
  - 适合:Tab 切换、搜索结果列表更新、大列表渲染

2. isPending 的作用

const [isPending, startTransition] = useTransition()

return (
  <div>
    <Tabs isPending={isPending} />
    {/* isPending=true 时显示加载状态 */}
  </div>
)

isPending 在 transition 开始时变为 true,所有 action 完成后变回 false。** 可以用来显示 loading 状态。

3. useTransition vs useDeferredValue

useTransition:
  - 控制 setState 的优先级
  - 需要 startTransition 包裹
  - 适合:你知道要更新什么状态

useDeferredValue:
  - 创建一个"延迟"版本的值
  - 不需要 startTransition
  - 适合:你只控制渲染结果的优先级
// useDeferredValue:输入框快速响应,搜索结果延迟更新
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)
useEffect(() => {
  fetchResults(deferredQuery)  // query 停止输入后才执行
}, [deferredQuery])

4. Transition 的关键特性

1. 可被打断:高优先级任务(用户输入)可以中断低优先级 transition
2. 不会卡顿:transition 中的 setState 不会阻塞输入
3. 自动去重:多个 startTransition 调用会被合并
4. async 支持:React 19 中 startTransition 可以接受 async 函数

5. 内部机制(Lanes 模型)

普通 setState → 分配到 SyncLane(最高优先级)
startTransition → 分配到 TransitionLane(低优先级)

Scheduler 通过 MessageChannel 插入宏任务
当前宏任务执行完 → 清空微任务 → 检查 React 任务
如果有高优先级任务(用户输入)→ 打断当前 transition
如果没有 → 继续执行 transition

核心一句话

useTransition 把 setState 标记为低优先级(Transition Lane),让 React 可以在用户交互时打断它。isPending 告诉你 transition 是否在进行中。本质是 lanes 优先级模型的上层 API。


Separating Events from Effects(事件与 Effect 分离)

来源:react.dev/learn/separating-events-from-effects

核心观点:事件处理器处理用户操作,Effect 处理同步。两者响应变化的方式不同

事件处理器:非响应式(只在用户交互时执行)
Effect:响应式(依赖值变化时重新执行)

1. 什么时候用事件处理器,什么时候用 Effect

✅ 事件处理器:用户点击按钮发送消息
   → 只在用户点击时执行,不会因为 state 变化自动执行

✅ Effect:组件连接到聊天室
   → 不管用户怎么操作,只要 roomId 变了就需要重新连接

判断标准:这段代码是“响应用户操作”还是“保持同步”?

2. 响应式 vs 非响应式

事件处理器里的逻辑 = 非响应式
  sendMessage(message) 只在用户点击时执行
  即使 message 变了,也不会自动重新发送

Effect 里的逻辑 = 响应式
  createConnection(roomId) 在 roomId 变化时重新执行
  必须在 deps 里声明 roomId

3. 问题:Effect 里混合了响应式和非响应式逻辑

// 🔴 theme 变了会导致重新连接(不需要)
useEffect(() => {
  const connection = createConnection(serverUrl, roomId)
  connection.on('connected', () => {
    showNotification('Connected!', theme)  // theme 是响应式值
  })
  connection.connect()
  return () => connection.disconnect()
}, [roomId, theme])  // theme 变了会重新执行整个 effect

问题: theme 变了不应该重新连接,只应该改变通知的颜色。但因为 theme 在 effect 里被读取了,React 要求你把它放进 deps。

4. 解决方案:Effect Events(useEffectEvent)

// ✅ 用 useEffectEvent 把非响应式逻辑提取出来
const onThemeChange = useEffectEvent((theme) => {
  showNotification('Connected!', theme)
})

useEffect(() => {
  const connection = createConnection(serverUrl, roomId)
  connection.on('connected', () => {
    onThemeChange(theme)  // theme 不在 deps 里
  })
  connection.connect()
  return () => connection.disconnect()
}, [roomId])  // 只依赖 roomId

useEffectEvent 是一个实验性 API(React 19+):**

  • 把回调包装成“Effect Event”

  • Effect Event 可以读取最新的 props/state,但不参与依赖追踪

  • Effect 只在 roomId 变化时重新执行

  • Effect Event 内部读到的 theme 永远是最新的

5. 总结:什么时候用什么

事件处理器:
  - 用户点击按钮
  - 用户提交表单
  - 用户输入
  → 只在交互时执行,非响应式

Effect:
  - 连接/断开 WebSocket
  - 订阅事件
  - 操作 DOM(如修改 title)
  → 依赖值变化时重新执行,响应式

Effect Events(useEffectEvent):
  - Effect 里需要调用非响应式逻辑
  - 不希望某个值的变化触发 effect 重新执行
  → 提取成 Effect Event,脱离依赖追踪

核心一句话

事件处理器处理“用户做了什么”(非响应式),Effect 处理“系统需要保持什么状态”(响应式)。当 Effect 里混入了不需要响应的逻辑时,用 useEffectEvent 提取出来,避免不必要的重新执行。

React Server Components 核心系列

这是 Dan 近两年最重要的输出,理解 RSC 的底层设计哲学。

1. The Two Reacts(2024.01)

来源:overreacted.io/the-two-reacts/

核心观点:React 的两个范式——客户端的 UI = f(state) vs 服务端的 UI = f(data)

客户端范式:********UI = f(state)

Counter 组件:count 在用户电脑上,setState 立即响应
→ 需要零延迟交互(拖拽、输入、hover)
→ 组件必须跑在客户端
→ 状态是"快照",闭包捕获当时的值

服务端范式:********UI = f(data)

PostPreview 组件:读文件系统、计算字数
→ 数据在服务端,组件跑在数据旁边
→ 构建时就执行完了,客户端只拿到渲染结果
→ 没有额外 API 请求,数据"已经在那了"

矛盾: 两个范式看似互斥——Counter 必须跑在客户端,PostPreview 必须跑在服务端。

真正的公式:********UI = f(data, state)

理想情况:一个编程范式同时处理 data 和 state
问题:如何把 f 拆分到两个不同的运行环境?
→ 这就是 RSC 要解决的问题

核心一句话

React 面临两难:交互需要客户端即时响应,数据处理需要服务端访问资源。RSC 的目标是让一个组件树能跨越两台计算机,同时保持 React 的组合模型。

2. React for Two Computers(2025.04)

来源:overreacted.io/react-for-two-computers/

核心观点:从标签 vs 函数调用的直觉出发,推导出 RSC 的设计

标签 vs 函数调用:

标签(JSX):名词,描述结构,像蓝图(blueprint)
函数调用:动词,描述过程,像菜谱(recipe)

标签天然适合深层嵌套(可读性好)
函数调用天然适合顺序执行
→ React 结合了两者:JSX 是声明式蓝图,事件处理器是命令式菜谱

蓝图 = 潜在的菜谱:

蓝图是把"时间"抽离后的菜谱
标签是把"执行"抽离后的函数调用
→ 标签是潜在的函数调用(tag is a potential function call)

RPC 的演进:

问题:函数在另一台电脑上
原始方案:回调函数 → 代码缠绕(callback hell)
解法1:async/await → 解决代码缠绕
解法2:import rpc → 像本地 import 一样调用远程函数
→ import rpc 语法:import rpc { prompt, alert } from './stuff'

单向通信(Server → Client):

如果另一台电脑不能"回话"怎么办?
→ 不能 await,不能拿返回值
→ 只能传递"潜在的调用":alert⧼'Hello'⧽
→ 这些潜在调用可以编码为 JSON:
  { fn: 'alert', args: [{ fn: 'prompt', args: ['Who?'] }] }
→ 这就是 RSC 协议的雏形!

核心一句话

标签是潜在的函数调用,蓝图是潜在的菜谱。RSC 把这个直觉推向极致:服务端组件是“潜在的客户端组件”,通过 JSON 协议跨越网络边界。

3. What Does “use client” Do?(2025.04)

来源:overreacted.io/what-does-use-client-do/

核心观点:'use client''use server' 是模块系统级别的创新

传统方式(stringly-typed):

后端:app.post('/api/like', handler)
前端:fetch('/api/like', { body: JSON.stringify(...) })

问题:
- 字符串拼接,没有类型检查
- API 路由和前端调用之间没有直接引用关系
- 找不到引用、重构困难

'use server'********:typed fetch()

// 后端
'use server';
export async function likePost(postId) { ... }

// 前端
import { likePost } from './backend';
await likePost(postId);  // 自动变成 HTTP 调用

本质:把 RPC 做进了模块系统
- import 不再只是"导入代码",而是"跨网络调用"
- 类型安全、可静态分析、可用 Find All References

**'use client'****:typed **<script>

// 客户端组件
'use client';
export function LikeButton({ postId, likeCount }) { ... }

// 服务端
import { LikeButton } from './frontend';
<LikeButton postId={42} likeCount={8} />

本质:把 <script> 标签做进了模块系统
- import 得到的不是函数本身,而是"客户端引用"(地址)
- 服务端可以渲染它(预渲染),也可以只发引用(lazy load)

两个世界的两扇门:

'use client'  → 从服务端到客户端的门(发送 <script>)
'use server'  → 从客户端到服务端的门(发送 fetch)

两者都不执行代码,只是建立"引用"
import 从一侧拿到的是另一侧的"地址"
→ 模块系统跨越了网络边界

核心一句话

'use client' 是有类型的 <script>'use server' 是有类型的 fetch()。它们把客户端/服务端的边界表达在模块系统里,让 import 成为跨越网络的桥梁。

4. Impossible Components(2025.04)

来源:overreacted.io/impossible-components/

核心观点:RSC 让你能写出“不可能”的组件——同时包含服务端数据和客户端交互

核心模式:Backend 渲染 Frontend

// 后端:读数据
async function GreetingBackend() {
  const myColor = await readFile('./color.txt', 'utf8')
  return <GreetingFrontend color={myColor} />
}

// 前端:处理交互
'use client'
function GreetingFrontend({ color }) {
  const [yourName, setYourName] = useState('Alice')
  return <p style={{ color }}>Hello, {yourName}!</p>
}

关键特性:

1. 后端先执行 → 数据"已经在那了",没有 loading 闪烁
2. 前端接管交互 → 状态隔离,每个实例独立
3. 组合性 → <Welcome /> 内部包含多个 GreetingBackend,无需手动协调
4. 自包含 → 一个标签搞定一切,局部数据 + 局部状态

进阶:SortableFileList

后端:readdir(directory) → 获取文件列表
前端:useState → 排序/过滤

一个组件同时做到:
- 读文件系统(只有服务端能做)
- 实时过滤排序(只有客户端能做)
- 单次往返,零额外请求

核心一句话

Impossible Component = Backend 数据 + Frontend 交互,封装在一个自包含的抽象里。局部数据,局部状态,一次往返。

5. JSX Over The Wire(2025.04)

来源:overreacted.io/jsx-over-the-wire/

核心观点:用 BFF + JSX 替代 REST API,让 API 返回组件而不是 JSON

REST 的问题:

REST Resource 没有牢固的现实基础:
- 靠近 Model → UI 要多次请求
- 靠近 ViewModel → 维护困难,Resource 形状膨胀
- 本质上是在猜"资源"应该长什么样

ViewModel API(BFF 模式):

每个屏幕有自己的 ViewModel 端点:
GET /screens/post-details/123

服务端返回的数据 = 该屏幕所有组件需要的数据
→ 一个屏幕一个请求,不多不少
→ 数据形状由 UI 决定,不是由"资源"决定

JSX Over The Wire:

API 路由直接返回 JSX 而不是 JSON:
app.get('/api/likes/:postId', (req, res) => {
  res.json(<LikeButton totalLikeCount={...} isLikedByUser={...} />)
})

→ API 不再是"返回数据的端点"
→ API 变成"返回组件的端点"
→ 组件本身就是数据的 ViewModel

BFF 的核心价值:

1. 服务端靠近数据源 → 延迟低
2. 可以并行调用多个 REST API → 消除瀑布
3. 每个屏幕独立优化 → 不影响其他屏幕
4. 前端团队掌控数据获取 → 不依赖后端改 API

核心一句话

REST Resource 既不反映数据存储方式,也不反映 UI 展示方式。用 BFF 模式让每个屏幕拿到恰好需要的数据,用 JSX 替代 JSON 让组件直接跨越网络边界。

6. Functional HTML(2025.05)

来源:overreacted.io/functional-html/

核心观点:从零重新发明 HTML,一步步推导出 RSC 的设计

演进路线:

1. Server Tags → 自定义标签 = 函数,服务端调用并替换
2. Attributes → 传参,支持对象
3. JSON 输出 → 序列化为 JSON 而非 HTML,保留对象
4. Async Server Tags → 标签可以 async,读文件系统
5. Client References → 'use client' = 有类型的 <script>
6. Server References → 'use server' = 有类型的 fetch()
7. Client Tags → 客户端组件作为标签延迟执行
8. Full-Stack Tags → 同时组合服务端和客户端标签
9. Streaming → 异步标签不阻塞,Suspense 控制揭示时机

核心洞察:

- <script> 标签在我们的体系里是不必要的
- 'use client' 是 <script> 的模块系统表达
- 'use server' 是 fetch() 的模块系统表达
- HTML 只是 JSON 的一种表现形式
- 主要输出格式是 JSON,HTML 是可选的"渲染"

核心一句话

从 HTML 出发,加上函数调用、JSON 序列化、异步标签、客户端引用、服务端引用、streaming——最终自然推导出 RSC。RSC 不是凭空发明,而是 HTML 演化的必然结果。

7. Static as a Server(2025.05)

来源:overreacted.io/static-as-a-server/

核心观点:“静态”只是“提前运行的服务器”

任何"服务器"框架都能输出"静态"站点:
→ 在构建时运行服务器,对每个页面发一次请求,把响应存到磁盘

"静态"和"服务器"不是两种工具,而是同一套代码的两种运行模式

Hybrid 框架的优势:

- 减少工具碎片化(一个生态系统搞定)
- 按路由选择 server 或 static
- 可以渐进式迁移(先静态,后加动态页面)
- 代码和插件在两种模式间共享

Dan 的博客就是例子:

用 Next.js + RSC 写的 → 但输出是纯静态站点
部署在 Cloudflare 免费静态托管 → 成本 $0
React Server Component 在构建时运行 → 不需要真正的服务器

核心一句话

“静态”是“服务器”的特例——提前运行、结果存盘。RSC 代码写一次,server 和 static 两种模式都能跑。

8. One Roundtrip Per Navigation(2025.05)

来源:overreacted.io/one-roundtrip-per-navigation/

核心观点:导航应该一次往返拿到所有数据,RSC 天然做到这一点

演进:从一次请求到 N 次请求再到一次请求

HTML 时代:一次请求,数据嵌在 HTML 里 ✓
REST + 客户端渲染:N 个 API 请求 + 客户端瀑布 ✗
Server Loader:服务端并行请求,一次返回 ✓
Server Functions(colocated):回到了 N 次请求 ✗

数据获取策略对比:

组件内 fetch (useEffect):
  ✗ 请求散布在组件树中,瀑布不可见
  ✗ 性能问题难以审计

React Query (useQuery):
  ✗ 仍然是客户端发起,瀑布仍在
  ✓ 缓存有帮助,但不解决根本问题

Server Loader (路由级):
  ✓ 消除瀑布,一次往返
  ✗ 丧失 colocation(数据逻辑和组件分离)

Server Functions (组件级):
  ✓ 恢复 colocation
  ✗ 但回到了客户端瀑布问题

GraphQL Fragments 的思路:

组件声明自己需要什么数据(fragment)
→ 服务端收集所有 fragment → 合并为一次查询
→ 既保持 colocation 又消除瀑布

RSC 的做法:

Server Components 天然在服务端执行
→ 数据获取在服务端,靠近数据源
→ 一次渲染 = 一次往返
→ 不存在客户端瀑布问题
→ Suspense 处理渐进式揭示

核心一句话

客户端数据获取永远面临“colocation vs 性能”的矛盾。RSC 通过让组件在服务端执行,从根本上消除了这个矛盾——数据获取天然在服务端,天然一次往返。

9. Why Does RSC Integrate with a Bundler?(2025.05)

来源:overreacted.io/why-does-rsc-integrate-with-a-bundler/

核心观点:RSC 需要打包器来高效地“序列化模块”

问题:如何序列化一个模块?

不能把代码内嵌为字符串(eval 不安全,重复传输)
→ 应该引用打包后的文件:'/chunk123.js#Counter'
→ 客户端按需加载对应 chunk

打包器在 RSC 中的三个职责:

1. 构建时:找到 'use client' 文件,创建 chunk 入口
2. 服务端:教 React 如何序列化模块引用(如 'chunk123.js#Counter')
3. 客户端:教 React 如何按引用加载模块

serialize / deserialize API:

服务端:
  import { serialize } from 'react-server-dom-webpack'
  const output = serialize(<Counter initialCount={10} />)

客户端:
  import { deserialize } from 'react-server-dom-webpack/client'
  const tree = deserialize(output)  // → <Counter initialCount={10} />
  root.render(tree)

核心一句话

RSC 不仅发送数据,还发送代码。打包器负责把 'use client' 组件变成可引用的 chunk,让服务端能序列化模块引用、客户端能按需加载。

10. How Imports Work in RSC(2025.06)

来源:overreacted.io/how-imports-work-in-rsc/

核心观点:RSC 扩展了 import/export 的语义,让两个环境共享模块系统

JavaScript 模块系统基础:

import 本质上像"复制粘贴",但有关键区别:
- 模块是单例(每个模块最多执行一次)
- 不同文件 import 同一模块 → 共享同一份代码
- 最终效果等价于把所有模块拼成一个文件

两个环境,两个模块系统:

后端 import a.js → a.js 的代码进入后端
前端 import a.js → a.js 的代码进入前端

两边各有"自己的版本",不共享执行上下文
→ 同一个模块可以在两边都存在,但各自独立

server-only 包(毒丸机制):

import 'server-only'  // 标记为服务端专用

如果前端打包器检测到这个 import → 构建失败
→ 防止服务端密钥/代码泄漏到客户端

RSC 的 import 语义扩展:

从 'use client' 文件 import:
  → 不执行代码,只拿到客户端引用(地址)
  → 运行时按地址加载模块

从 'use server' 文件 import(从客户端):
  → 不执行代码,只拿到异步函数
  → 调用时自动发 HTTP 请求

同一个文件从不同侧 import → 不同行为
→ import 的语义取决于"谁在 import"

核心一句话

RSC 不发明新概念,而是扩展 import 的语义:同一份 import,在服务端是“直接引用”,在客户端是“跨网络调用”。两个环境共享模块系统,但各自独立执行。

11. Progressive JSON(2025.05)

来源:overreacted.io/progressive-json/

核心观点:RSC 用“渐进式 JSON”传输组件树,一个慢部分不阻塞其他部分

传统 JSON 的问题:

必须等最后一个字节到齐才能 parse
→ 一个慢的部分(如数据库查询)阻塞整棵树
→ 客户端什么都做不了,直到服务端完成所有工作

Progressive JSON(广度优先传输):

第一层(外壳):
  { header: "$1", post: "$2", footer: "$3" }
  → 客户端立刻拿到 header 和 footer

第二层(填充):
  /* $2 */ { content: "$4", comments: "$5" }
  → post 的结构出来了,但 comments 还是 Promise

第三层(完成):
  /* $5 */ ["$6", "$7", "$8"]
  → comments 填充完毕

关键设计:

1. 占位符($1, $2...):未到达的部分用 Promise 表示
2. 乱序到达:不需要按顺序,客户端按 ID 填充
3. 内联优化:小对象直接内嵌,大对象 outline 为独立行
4. 去重:相同对象只传一次,其他地方引用
5. 循环引用:支持(通过行号引用)

Streaming Data vs Streaming UI:

数据:渐进式到达(RSC 协议自动处理)
UI:由 <Suspense> 控制揭示时机

Promise = throw(数据还没到)
<Suspense> = catch(展示 fallback)

数据传输和 UI 揭示是解耦的
→ 数据尽快到达,UI 按设计意图揭示

核心一句话

Progressive JSON = 广度优先传输 + 占位符 Promise。数据尽快到达,一个慢部分不阻塞其他部分,<Suspense> 控制用户看到什么。

12. Introducing RSC Explorer(2025.12)

来源:overreacted.io/introducing-rsc-explorer/

核心观点:RSC Explorer 是一个可视化工具,展示 RSC 协议的实际工作方式

工具特点:

- 纯客户端运行(Server 部分在 Worker 里)
- 使用 React 官方的 serialize/deserialize 包
- 逐步执行,可视化 RSC 协议流
- 源码完全开源

可以探索的场景:

- Hello World:JSX 如何跨越网络
- Async Component:Streaming + Suspense 的实际效果
- Counter:客户端引用如何传输
- Form Action:Server Action 如何工作
- Router Refresh:无框架的 RSC 路由实现
- Pagination / Error Handling / Binary Data
- CVE-2025-55182(安全漏洞复现)

访问地址: https://rscexplorer\.dev/

核心一句话

RSC Explorer 是理解 RSC 协议的最佳可视化工具——逐步执行、实时查看序列化/反序列化过程,纯客户端运行。

13. How to Fix Any Bug(2025.10)

来源:overreacted.io/how-to-fix-any-bug/

核心观点:系统化的 bug 修复流程——找复现、缩复现、删代码、找根因

四步流程:

Step 0: 不要让 AI 直接修(它没有复现,会反复说"修好了"但没有)

Step 1: 找复现(Repro)
  - 明确:做什么 → 期望什么 → 实际什么
  - 可靠复现 = 每次都能触发

Step 2: 缩窄复现
  - 把视觉问题转化为可测量的问题
  - "滚动抖动" → "scroll position 没变"
  - 关键:确认正向结果也能获得(注释掉代码能修好)
  - 否则可能在修一个无关的问题

Step 3: 逐步删除
  1. 跑复现,确认 bug 存在
  2. 删一段代码
  3. 再跑复现
  4. bug 还在 → 提交
  5. bug 消失 → 记录理论,回滚,删更小的块
  
  关键:始终保持一个"bug 仍在发生"的检查点
  → 像 well-founded recursion,每步都必须"更小"

Step 4: 找根因
  - 删到最小可复现 → 分析代码或依赖
  - 本例:React Router 的 ScrollRestoration bug

常见错误:

✗ 删着删着开始"测试理论",创建新的复现案例
  → 新案例可能跟原 bug 无关
  → 正确做法:回到原复现,继续删

✗ 让 AI 直接修
  → AI 没有可靠的复现验证手段
  → 先把复现转化为 AI 可测量的形式

核心一句话

修 bug 的秘诀:找到可靠复现 → 转化为可测量的形式 → 逐步删除代码同时确认 bug 仍在 → 直到删无可删,根因自然浮现。

14. Suppressions of Suppressions(2025.06)

来源:overreacted.io/suppressions-of-suppressions/

核心观点:用 lint 规则禁止 suppress 其他 lint 规则

问题:

Lint 规则有时是错的 → 需要 suppress(注释禁用)
但有些规则绝对不能 suppress(会导致线上事故)
→ 需要一种机制防止误 suppress

解法:suppress 规则的规则

引入一条新 lint 规则:禁止 suppress 某些特定规则
→ 如果你想 suppress 一个"不可 suppress"的规则
→ 会触发新的 lint 错误

进一步:禁止 suppress 这条规则本身(双重 suppress)
→ 只能通过 code review + 人工审批

实际应用(Facebook):

- 某些规则的 suppress 需要链接到 ticket
- 双重 suppress 需要 infra 团队审批
- 自动化 grep 检测新提交的双重 suppress
- 关键规则的 suppress 可以阻止合并

更广的意义:

Lint 规则背后是"社会契约":
- 如何版本管理软件
- 文件结构如何映射组织结构
- 如何分配共担责任
- 如何在演进模式的同时避免事故

核心一句话

有些 lint 规则绝对不能被 suppress——用另一条 lint 规则来禁止 suppress,形成“suppressions of suppressions”。本质上是用工具编码社会契约。

核心一句话总结(全部 14 篇)

RSC 系列(12篇):
  React 正在把客户端/服务端的边界做进模块系统。
  'use client' = typed <script>,'use server' = typed fetch()。
  组件可以跨越两台计算机,数据渐进式传输,一次往返搞定。
  "静态"只是"提前运行的服务器"。

How to Fix Any Bug:
  找复现 → 缩复现 → 逐步删除 → 根因浮现。
  始终保持一个"bug 仍在"的检查点。

Suppressions of Suppressions:
  用 lint 规则禁止 suppress 关键 lint 规则。
  工具编码社会契约。