今年四月份,React 19 beta 版本发布。让我们了解到了 React 19 的大量新特性。因此,许多群友都在等正式版的发布,本来以为这一天不会等太久,万万没想到,这一等,就是八个月。
React 19 正式版发布之后,大家都挺激动的,昨天半夜,群里就有大佬在群里通知了这个消息。
今天早上起来就看到了消息,马不停蹄的把我的项目升级了 usehook.cn,该项目之前是基于 react 19 beta 版本构建,所以也没遇到啥毛病,改个版本号就升级成功了。
关于 React 19 的大多数内容,我已经写了大量的文章跟大家分享,因此这里就不再赘述,不然又是把官方文档上的内容复制一遍。
我们今天主要关注的是,和之前发布的 beta 版本相比,React 19 正式版都新增了什么内容?
一、Suspense 改动
此前关于 Suspense 有过一波争议,例如在下面的代码写法中,Suspense 包裹了多个携带了 use 逻辑的子组件。此时会发起两个接口请求,但是这两个接口请求是串行请求的。从而导致一部分开发者认为网站的速度变慢了,因为我们使用并行请求的话,响应速度会快很多。
<Suspense fallback={<Skeleton />}>
<List promise={promise} />
<List2 promise={promise2} />
</Suspense>
但是对此我有不同的看法,我认为最佳实践里,一个 Suspense 中只应该套一个子组件,这个一对一的关系被打破的话会导致业务逻辑变得更加复杂和不可控。因此一直没有太把这个点放在心上。
不过正式版的这次改动中,依然对 Suspense 包含多个子节点的情况进行了调整。可惜的是,在我的验证中,并没有调整为接口并行请求。
我的完整测试代码如下:
export default function Demo01() {
const [promise, update] = useState(() => fetchList(5))
const [promise2, update2] = useState(() => fetchList2(5))
function __inputChange(e) {
const len = e.target.value.length % 10
update(fetchList(len))
update2(fetchList2(len))
}
return (
<div>
<Input onChange={__inputChange} placeholder='Enter something' />
<Suspense fallback={<Skeleton />}>
<List promise={promise} />
<List2 promise={promise2} />
</Suspense>
</div>
)
}
多次测试结果表明,目前依然是串行请求。而并没有调整成为部分开发者预期的并行请求。
但是在官方文档的更新日志中,明确的说明了 Suspense 的改动。
然后我又仔细阅读了一下,发现他说的实际上是另外一回事。
情况是这样的,当我们使用 Suspense 时,他的子组件也会正常被渲染执行。因此 fallback 组件并不是第一时间就能展示出来,而是等到子组件执行报错之后,才能知道此时应该显示一个 fallback 占位。
那么当 Suspense 有多个子组件之后呢,在以前的情况是,要等到所有的子组件都执行完毕之后,才会展示 fallback。
那如果后续的子组件执行时间比较长,fallback 的展示就会延后。
而现在则调整成为,当第一个子组件执行报错之后,此时就会立即提交回退,让 fallback 对应的组件渲染到页面上。然后再渲染后续的兄弟节点。
当然,如果我们秉持一个理念,在使用中让 Suspense 始终只有一个子节点,那么这个问题在之前也不会对我们造成影响。
然后接口串行还是并行,这个最好是由我们开发时自己来控制。就可以不用受到该机制的额外影响。
二、新增两个静态 API
新增了两个 react-dom/static 用于静态站点生成的 API。
- prerender
- prerenderToNodeStream
这两个 API 主要是为了强化 React 在流式传输上的理念。流式传输让我们不需要等待资源加载完成才能对它进行渲染,浏览器只需要加载其中一个分块,就可以执行逻辑。因此,流式传输非常有利于更快的展示页面。
此前 Next.js 就在这个理念之上做了很多工作,全面拥抱流式传输是未来的发展方向之一。
prerender
prerender 会等待所有数据加载完成之后再解析,因此它非常适合用于生成静态的 SSG。
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
中需要包含整个 html 文档。
export default function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}
另外的 doctype 和 script 标签,则会被 React 自动注入进来。
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>
然后在客户端,则需要使用水合的 api 来渲染 App。
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
需要特别注意的是,prerender 会等待所有数据加载完成,才会生成静态 html。这里也包括 Suspense。
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
从能力上来看,此功能主要是为 Next.js 提供更加便捷的 SSG 能力,让开发者在应用层的组件写法更加趋于一致性。因此这是一个非常底层的 API。大家了解一下即可。
✓
更详细的知识大家可以通过官方文档了解 https://react.dev/reference/react-dom/static/prerender。
prerenderToNodeStream
prerenderToNodeStream 与 prerender 的用法几乎是一样的。不过不同的是,该 API 仅用于 node.js。而 prerender 则用于具有 web streams 的环境,例如 Deno、Edge 等边缘运行时。
这里额外提一下,在当前,边缘渲染是未来服务端渲染的主流发展方向之一。国内外的云平台几乎都积极支持了 Next.js 的边缘渲染能力。在许多大厂团队里,边缘渲染已经成为了主流的渲染方案,只是传播速度还没那么快,许多中小团队的前端开发对此概念还一无所知。
三、总结
React 19 正式版并没有特别大的改动,因此对我个人而言,并不代表什么,因为我已经在好多个项目中,直接使用 React 19 的 beta 版和 rc 版开发应用了。
但是稳定版的发布,可以让大家都放心大胆的使用了,毕竟我目前的项目都是个人项目,随便用都无所谓。所以对于群友来说,正式版的还是很有意义的,大家都非常激动,在群里奔走相告。
总的来说,半年多的使用感受下来,React 19 改变了我的开发方式。他在弱化 useEffect 上面做了非常多行之有效的工作,让我的代码可读性变得更高。
加上还有 React Compiler 的加持,对于初级开发者来说,React 的性能问题也能得到很大的提高。
不过呢,一个令人很可惜的消息是,antd 在兼容 React 19 上面可能遇到了一点麻烦,暂时还不能立即结合 React 19 来使用。根据他们团队成员的反馈来看,好像目前暂时并没有找到一个合适的方案去解决兼容的 render 消失的问题。这里主要的问题是 React 19 彻底删出了以前的同步模式入口,默认为入口并发模式,现在只能用如下的方式创建项目,从而要横跨 react 16 到 react 19 就变得很麻烦。
import {createRoot} from 'react-dom/client'
当然,我的期待是重启一个大版本,直接放弃对老版本的支持,搞一个更轻量的版本出来。