先厘清一个容易混淆的点:Redact 不是另一个前端框架,也不是让项目“去 React 化”。它换掉的是浏览器里的 React 运行时,组件、hooks、JSX 写法都保留。
TanStack Redact 0.2.0 在 2026 年 10 月 26 日发布,第一次把 TanStack Start、Vite、Vinext、Next.js 四个浏览器集成一起标为 stable。官方给出的客户端产物降幅从 20.4% 到 70.5%,差距取决于框架和构建方式。
下面按“是什么、怎么做、怎么接、什么时候用”四部分拆开讲。
一、React 是什么,Redact 又是什么
React 是一个用组件描述 UI 的 JavaScript 库。你写函数组件并返回 JSX,状态变化时 React 重新执行组件、计算新的 UI,再把变化提交到 DOM。
真正跑在浏览器里的,主要是两个包:
react:组件模型、hooks、context、Suspense 等 API。react-dom:把 React 元素渲染成 DOM,负责事件、表单、hydration 等。
这两个包加上调度逻辑,构成了浏览器端的 React 运行时。React 19 的完整运行时,生产 ESM bundle gzip 后约 69 KB。
Redact 做的事情,是用一套更小的实现来提供同样的 React API。你继续写:
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
构建之后,浏览器里执行这段代码的是 Redact,不是 React。服务端该用 React 的地方仍然用 React。
它的定位可以概括成一句话:
保留 React 的写法,替换浏览器里的运行时,用并发能力换更小的体积。
二、原理是什么
1. 构建期替换,而不是运行时切换
Redact 通过 Vite 插件或 Next.js 配置,把浏览器产物里的 react、react-dom 导入指向自己的实现。
源码 import 'react'
│
├─ 服务端:React (SSR / RSC / Server Actions)
└─ 浏览器:Redact (hydration + 客户端渲染)
这不是 polyfill,也不是在代码里判断环境。构建工具在打包阶段就完成了入口替换,所以业务代码不需要改。
在 Next.js 和 Vinext 里,边界划得很清楚:
- 服务端渲染、Server Components、Server Action 传输,仍然用原生 React。
- 浏览器端的 hydration 和渲染,交给 Redact。
- 路由、缓存、数据加载仍由框架自己负责。
2. 重新实现 React 的 API 表面
Redact 覆盖了日常 React 应用用到的大部分能力:
- JSX、函数组件、class 组件
useState、useEffect、useMemo、useRef、useContextuseSyncExternalStore、useActionState、useOptimistic- context、refs、memo、lazy、portals、错误边界
- Suspense、流式 hydration、表单、事件系统
- React Compiler 的 memo 缓存入口
它不是简单地把 React 包一层,而是自己实现渲染、更新、事件和 hydration。官方兼容性表也明确标注了差异,例如部分并发行为、缓存 API、View Transition 动画并不完全等价。
3. 同步渲染,砍掉调度器
这是 Redact 体积变小的核心原因,也是它最大的取舍。
React 的并发渲染包含时间切片、优先级调度、可中断的组件渲染。一个重渲染可以被拆开,让浏览器先处理输入,再继续渲染。
Redact 采用同步渲染:更新一来,就一路算完、提交完。实现更简单,运行时更小,但重渲染会占住主线程。
官方原话是:Suspense、transition、pending form、optimistic state 都有覆盖,但一次很重的渲染仍然可能挡住输入。
所以 Redact 不是“全面更快的 React”,而是“用调度能力换体积和同步更新”。
4. 按需裁剪特性
Vite 和 TanStack Start 可以关掉用不到的特性:
redact({ features: { hydration: false, classComponents: false } })
redact({ preset: 'nano', features: { context: true } })
nano 预设默认关闭所有可选能力。关得越多,体积越小,但行为也会相应减少。Next.js 和 Vinext 使用的是 full 运行时,不适用 Vite 的 feature preset。
三、怎么接入
Vite
// vite.config.ts
import { defineConfig } from 'vite'
import { redact } from '@tanstack/redact/vite'
export default defineConfig({
plugins: [redact()],
})
TanStack Start
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import react from '@vitejs/plugin-react'
import { redact } from '@tanstack/redact/vite'
export default defineConfig({
ssr: { noExternal: true },
plugins: [tanstackStart(), react(), redact()],
})
ssr.noExternal: true 是为了让 Vite 也重定向服务端依赖里的 React 导入。
Next.js
// next.config.mjs
import { withRedact } from '@tanstack/redact/next'
export default withRedact({
// 原有的 Next.js 配置
})
0.2.0 的 withRedact 支持 Next.js 15.5 以及 16.0 到 16.4,Turbopack 和 Webpack 都可以,App Router、Pages Router、纯 Pages Router 应用都覆盖。
Vinext
// vite.config.mjs
import vinext from 'vinext'
import { redact } from '@tanstack/redact/vinext'
export default { plugins: [vinext(), redact()] }
两个必须注意的点
react和react-dom仍然要安装,服务端还要用。- 在 Next.js 里不要为了 Redact 修改
jsxImportSource。服务端仍然需要 React 的 JSX runtime。
四、省了多少,代价是什么
官方用同一个应用分别构建 React 版和 Redact 版,把所有客户端 JS 和 CSS 的 gzip 体积相加:
| 框架 | React | Redact | 节省 |
|---|---|---|---|
| TanStack Start | 109.3 KB | 62.3 KB | 47.0 KB,43.0% |
| Vite | 68.3 KB | 20.1 KB | 48.1 KB,70.5% |
| Vinext | 138.1 KB | 98.8 KB | 39.3 KB,28.5% |
| Next.js | 175.6 KB | 139.9 KB | 35.7 KB,20.4% |
这是整个构建的总量,包含懒加载 chunk,不是单个页面的下载量。Start 和 Vite 继续关闭用不到的特性后,示例里还能降到 59.8 KB 和 12.6 KB。
只看运行时:
| 运行时 | gzip 体积 | 相对 React |
|---|---|---|
| React 19.3.0 | 69,162 B | 基准 |
| Redact 默认 Vite 配置 | 23,311 B | 小 66.3% |
| Redact 开启原生动画 | 28,159 B | 小 59.3% |
代价同样具体。在官方微基准里,稀疏状态更新比 React 快 71.9%,混合深度更新快 59.1%;但 hydration 慢 21.4%,passive effects 慢 27.0%,Suspense retry 慢 124.8%。
Suspense retry 的 124.8% 看起来夸张,绝对值是每个 cycle 多 0.15 ms 左右,而且一个 cycle 包含 96 个组件、192 次 reducer action 和隐藏、重试、显示等流程。它不是“交互慢一倍”。但树更大、重试更多、设备更慢时,差距会放大。
另外,Redact 挂载后保留的 JS 堆也比 React 高:666.4 KiB 对 762.5 KiB。体积省在下载,内存不一定省。
0.2.0 之后,0.2.1、0.2.2、0.2.3 在三天内连续补了 class 组件默认 props、受控输入、hydration、Suspense、bigint 子节点等兼容细节。截至现在,npm latest 是 0.2.3。
五、适合和不适合的场景
适合
- 客户端 bundle 是主要痛点、希望直接减少下载量的 React 项目。
- 用 TanStack Start、Vite、Vinext 或 Next.js,且能接受同步渲染取舍的团队。
- 页面以常规表单、列表、路由切换为主,对并发渲染依赖不强的应用。
- 想先在内部页面或低风险路由上做 A/B 验证,再决定是否全量切换的团队。
不适合
- 严重依赖并发渲染、可中断渲染、复杂 Suspense 调度的应用。
- 对 Safari、Firefox、移动端有硬性要求,但还没做完整回归的项目。官方明确说这些场景仍有未验证部分。
- 服务端渲染是主要瓶颈的团队。Redact 优化的是浏览器端,Next.js 和 Vinext 的 SSR 仍然走 React。
- 需要完整 React 行为等价、不能接受任何调度差异的项目。
六、怎么开始验证
不要直接改生产配置。先在一个分支上做三件事:
- 记录当前构建的客户端 JS/CSS gzip 总量,按路由拆开。
- 接上 Redact,跑同样的构建,对比总量和首屏路由。
- 用真实设备跑登录、表单、长列表、路由切换和 Suspense 回退。
Vite 项目成本最低:加一个插件,构建一次,体积数字立刻可见。Next.js 和 Vinext 的收益小一些,但存量项目多,20% 左右的客户端体积下降已经值得验证。
Redact 0.2.0 的意义不是“React 不行了”,而是多了一个明确的选项:保留 React 的写法,换掉浏览器里的运行时,用并发能力换体积。这笔交换是否划算,取决于你的应用把时间花在哪里。