苦等八個月,React 19 穩定版,它終于來了!我的項目已升級!
今年四月份,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 包含多個子節點的情況進行了調整??上У氖牵谖业尿炞C中,并沒有調整為接口并行請求。
我的完整測試代碼如下:
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'
當然,我的期待是重啟一個大版本,直接放棄對老版本的支持,搞一個更輕量的版本出來。