Preact 11 到底改了什么:async 水合从 1 个 DOM 节点放开到 0 个或多个,preact/compat 单出口包最多瘦 28%

Preact 11 在 9 月 29 日正式发布,把开了六年的 Road to Preact 11(GitHub issue #2621)一次性收尾,10 月 6 日登上 JavaScript Weekly #805 头条。对准备升级的人来说,最实在的三个数字是:async 水合的节点限制从「必须 1 个」放开到「0 个或任意多个」、forwardRef 这层包装从用法里直接消失、preact/compat 做成可 tree-shake 之后代表性单出口包最多瘦身 28%。

六年攒出来的 major

Preact X(10.x)从 2019 年活到现在,稳定线一路走到 10.29.8(2026-08-01),之后 npm 上陆续出现 11.0.0-rc.0(2026-08-10)、rc.1、rc.2 三个预发布,9 月 29 日官方博客官宣 Preact 11,如今 latest 标签已经指向 11.0.1——发版几天就出了一个 patch,说明有人已经在生产环境里踩了。

官方对这次发布的定位很克制:不是重写,而是「把六年来攒下的 breaking changes 打包成一个 major」。官网的升级指南(preactjs.com/guide/v11/upgrade-guide)也写明:多数用户的改动是类型相关的,运行时行为变更集中在下面三处。

变化一:Hydration 2.0,异步边界不再卡在 1 个 DOM 节点

before(Preact 10):Suspense 边界在水合过程中暂停后恢复时,必须正好渲染 1 个 DOM 节点。返回 null、Fragment 或者两个并列元素,水合都会对不上,客户端只能整段重渲染,SSR 出来的 HTML 等于白发。

// Preact 10:AsyncProfile 恢复后必须是恰好 1 个 DOM 节点
<Suspense fallback={<Spinner />}>
  <AsyncProfile />  {/* 返回 null 或 <><a/><b/></> → 水合 mismatch */}
</Suspense>

after(Preact 11):Hydration 2.0 允许挂起的组件返回 0 个或任意多个 DOM 节点,并且能和流式 SSR 的输出对上——同一个组件,恢复后返回 null、Fragment、多元素都可以安全水合。

// Preact 11:返回 null / Fragment / 多节点都能对上流式 SSR 的输出
<Suspense fallback={<Spinner />}>
  <AsyncProfile />  {/* 用户没资料时返回 null,也不再触发整段客户端重渲染 */}
</Suspense>

变化二:refs 默认透传,forwardRef 整层壳被删掉

before:函数组件拿 ref 必须包一层 forwardRef,每个需要暴露内部 DOM 的组件都要多写一层样板。

// Preact 10
const Field = forwardRef((props, ref) => <input ref={ref} {...props} />);

after:ref 就是一个普通 prop,函数组件直接解构,forwardRef 不再需要(React 19 同款行为,从 React 迁移过来的人少记一条规则)。

// Preact 11
function Field({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

变化三:hooks 依赖比较换成 Object.is,NaN 不再每次判定「变了」

before:hook 依赖用宽松相等比较,NaN == NaN 是 false,依赖数组里出现 NaN(比如解析失败的 Number(input))时,effect 每次渲染都重新执行。

after:Object.is(NaN, NaN) 为 true,useState 的初始值和 useEffect 的依赖数组里 NaN 都被稳定识别为「没变」,一次渲染只跑一次。

// Preact 11:input 解析失败时 deps=[NaN],不再无限重跑
useEffect(() => {
  fetchProfile(Number(userId));  // userId 非数字 → NaN
}, [Number(userId)]);

升级时真正会炸的几处

官方说「多数改动是类型相关的」,但有几处是实打实的运行时/工程配置变更,升级前要过一遍:

  1. ESM-only:preact@11.0.0 的 main 字段直接指向 dist/preact.mjs,而 preact@10.29.8 还是 dist/preact.js。老构建链里用 CommonJS require('preact') 的地方会直接失败,webpack 4 / 老 Node 脚本要先确认能解析 .mjs。
  2. TypeScript 5.1+ 强制:JSX 类型从全局 JSX namespace 挪进 preact namespace,useRef 要求必填初始值(对齐 React 19);类型报错集中在这两项,全局搜一下 JSX. 和无参 useRef() 就能清干净。
  3. IE11 正式出局,浏览器目标整体更新。
  4. 挪家的 API:defaultProps 和「自动补 px」都搬进了 preact/compat;component.base、SuspenseList、replaceNode 被彻底删除,用到就是编译期报错。
  5. 生态基本就绪:官方的 @preact/signals、preact-render-to-string、preact-iso、prefresh、@preact/preset-vite 从预发布阶段就支持 11,npm ls preact 看到的多数一方依赖不用等更新。

下一步怎么做

先别动业务代码,三条命令把升级面摸清:

npm view preact@latest version     # 当前 11.0.1
npx tsc --version                  # 必须 ≥ 5.1,否则先升 TS
npm ls preact                      # 看清哪些依赖还钉在 10.x

然后打开 preactjs.com/guide/v11/upgrade-guide,按 Hydration 2.0、refs 默认透传、Object.is 三个小节逐项核对自己项目的用法——这三节正好对应上面三个运行时变化,核对完再执行 npm install preact@11,一次编译通过的概率比直接装高得多。

评论区

0 条评论

登录后可评论。

阿监·前端工程化 225 阅读