React 渲染的未來
大家好,我是 CUGGZ。
在過去的幾年中,React 的流行度一直在增加,而且還在加速。React 每周的 npm 下載量超過 1400 萬次 ,React Devtools Chrome 擴(kuò)展有超過 300 萬 的周活躍用戶。
然而,在 React 18 之前,React 中的渲染模式幾乎是相同的。在本文中,我們將研究 React 當(dāng)前的渲染模式、它們存在的問題,以及 React 18 引入的新模式如何是解決這些問題的。
相關(guān)術(shù)語
在深入研究渲染模式之前,讓我們來看一下將在這篇文章中使用的一些重要術(shù)語:
Time To First Byte (TTFB) :發(fā)出頁面請求到接收到應(yīng)答數(shù)據(jù)第一個(gè)字節(jié)所花費(fèi)的時(shí)間;
First Paint (FP) :第一個(gè)像素對用戶可見的時(shí)間。
First Contentful Paint (FCP): 第一條內(nèi)容可見所需的時(shí)間。
Largest Contentful Paint (LCP) :加載頁面主要內(nèi)容所需的時(shí)間。
Time To Interactive (TTI): 頁面變?yōu)榻换ゲ⒖煽宽憫?yīng)用戶事件的時(shí)間。
當(dāng)前的渲染模式
目前,我們在 React 中使用的最常見的模式就是客戶端渲染和服務(wù)端渲染,以及由 Next.js等框架提供的一些高級形式的服務(wù)端渲染,例如靜態(tài)站點(diǎn)生成(SSG)、增量靜態(tài)生成(ISR)。我們將研究其中這其中的每一個(gè),并深入研究 React 18 引入的新模式。
客戶端渲染(CSR)
在 Next.js 和 Remix 等元框架出現(xiàn)之前,客戶端渲染(主要使用 create-react-app 或其他類似的腳手架)是構(gòu)建 React 應(yīng)用程序的默認(rèn)方式。
使用客戶端渲染,服務(wù)端只需要為包含必要<script>和<link>標(biāo)簽的頁面提供基本 HTML。一旦相關(guān)的 JavaScript 下載到瀏覽器。 React 渲染樹并生成所有 DOM 節(jié)點(diǎn)。 路由和數(shù)據(jù)獲取的所有邏輯也由客戶端 JavaScript 處理。
為了看看 CSR 是如何工作的,我們來渲染以下應(yīng)用:
<Layout>
<Navbar />
<Sidebar />
<RightPane>
<Post />
<Comments />
</RightPane>
</Layout>
渲染周期如下:
CSR渲染周期.gif
這是客戶端渲染的 Network 圖:
因此,CSR 應(yīng)用接收到應(yīng)答數(shù)據(jù)第一個(gè)字節(jié)很快(TTFB),因?yàn)樗鼈冎饕蕾囉陟o態(tài)資源。 但是,在下載相關(guān) JavaScript 之前,用戶必須盯著空白屏幕。 在那之后,由于大多數(shù)應(yīng)用都需要從 API 獲取數(shù)據(jù)并向用戶顯示相關(guān)數(shù)據(jù),這導(dǎo)致加載頁面主要內(nèi)容所需的時(shí)間(LCP)很長。
CSR 的優(yōu)點(diǎn)
由于客戶端渲染架構(gòu)包含靜態(tài)文件,因此可以非常輕松地通過 CDN 提供服務(wù);
所有渲染都是在客戶端完成的,因此 CSR 允許我們在不刷新整個(gè)頁面的情況下進(jìn)行導(dǎo)航,從而提供良好的用戶體驗(yàn)。
TTFB 時(shí)間很快,因此瀏覽器可以立即開始加載字體、CSS 和 JavaScript。
CSR 的缺點(diǎn)
由于所有內(nèi)容都在客戶端渲染,因此性能受到很大影響,因?yàn)橛脩羰紫刃枰螺d并處理它才能看到頁面上的內(nèi)容。
客戶端渲染應(yīng)用通常會在組件掛載時(shí)獲取所需的數(shù)據(jù),這會導(dǎo)致糟糕的用戶體驗(yàn),因?yàn)樵诔跏柬撁婕虞d時(shí)會遇到很多 loaders。 此外,如果子組件需要獲取數(shù)據(jù),情況可能會變得更糟,這樣它們的父組件獲取完所有數(shù)據(jù)后才會渲染它們,這可能會導(dǎo)致大量 loaders 和糟糕的 Network Waterfall。
SEO 是客戶端渲染應(yīng)用的一個(gè)問題,因?yàn)榫W(wǎng)絡(luò)爬蟲可以輕松讀取服務(wù)端渲染的 HTML,但它們可能不會等待下載完所有 JavaScript 包,執(zhí)行它們并等待客戶端數(shù)據(jù)獲取瀑布流完成 ,這可能會導(dǎo)致不正確的索引。
服務(wù)端渲染(SSR)
目前,在 React 中服務(wù)端渲染的工作方式如下:
通過 renderToString 獲取相關(guān)數(shù)據(jù)并在服務(wù)端為頁面運(yùn)行客戶端 JavaScript,這為我們提供了顯示頁面所需的所有 HTML。
將此 HTML 提供給客戶端,從而實(shí)現(xiàn)快速的 First Contentful Paint。
這時(shí)還沒有完成, 我們?nèi)匀恍枰螺d并執(zhí)行客戶端 JavaScript 以將 JavaScript 邏輯連接到服務(wù)端生成的 HTML 以使頁面具有交互性(這個(gè)過程就是“注水”)。
為了更好地理解它是如何工作的,讓我們來看一下上面例子中使用 SSR 時(shí)的生命周期:
<Layout>
<Navbar />
<Sidebar />
<RightPane>
<Post />
<Comments />
</RightPane>
</Layout>
渲染周期如下:
SSR渲染周期.gif
這是服務(wù)端渲染的 Network 圖:
因此,使用 SSR,我們可以獲得良好的 FCP 和 LCP,但 TTFB 會受到影響,因?yàn)槲覀儽仨氃诜?wù)端獲取數(shù)據(jù),然后將其轉(zhuǎn)換為 HTML 字符串。
現(xiàn)在,你可能會問這和 Next.js 的 SSG/ISR 有啥區(qū)別呢?它們也必須經(jīng)歷上面的過程。 唯一的區(qū)別是,它們不會受到 TTFB 時(shí)間較長的影響。因?yàn)?HTML 要么是在構(gòu)建時(shí)生成的,要么是在請求傳入時(shí)以增量方式生成和緩存的。
但是,SSG/ISR 更適合公共頁面。對于根據(jù)用戶登錄狀態(tài)或?yàn)g覽器上存儲的其他 cookie 更改的頁面,必須使用 SSR。
SSR 的優(yōu)點(diǎn)
與 CSR 不同,SEO 要好得多,因?yàn)樗?HTML 都是從服務(wù)端預(yù)先生成的,網(wǎng)絡(luò)爬蟲可以毫無問題地爬取它。
FCP 和 LCP 非常快。因此,用戶可以很快看到內(nèi)容,而不是像 CSR 應(yīng)用那樣查看空白屏幕。
SSR 的缺點(diǎn)
由于我們在每次請求時(shí)首先在服務(wù)端渲染頁面,并且必須等待頁面的數(shù)據(jù)需求,這可能會導(dǎo)致 TTFB 速度變慢。這可能是由多種原因?qū)е碌?,包括未?yōu)化的服務(wù)端代碼或者許多并發(fā)的服務(wù)端請求。不過,使用像 Next.js 這樣的框架可以提前生成頁面并使用 SSG(靜態(tài)站點(diǎn)生成)和 ISR(增量靜態(tài)站點(diǎn)生成)等技術(shù)將它們緩存在服務(wù)端,從而在一定程度上解決了這個(gè)問題。
即使初始加載速度很快,用戶仍然需要等待下載頁面的所有 JavaScript 并對其進(jìn)行處理,以便頁面可以重新注水并變得可交互。
新的渲染模式
上面,我們介紹了 React 中當(dāng)前的渲染模式是什么以及它們存在什么問題。 總結(jié)一下:
在 CSR 應(yīng)用中,用戶必須下載所有必要的 JavaScript 并執(zhí)行它以查看/與頁面交互。
在 SSR 應(yīng)用中,我們通過在服務(wù)端生成 HTML 來解決了其中的一些問題。然而這并不是最優(yōu)的,因?yàn)槭紫任覀儽仨毜却?wù)端獲取所有數(shù)據(jù)并生成 HTML。 然后客戶端必須下載整個(gè)頁面的 JavaScript。 最后,我們必須執(zhí)行 JavaScript 以連接服務(wù)端生成的 HTML 和 JavaScript 邏輯,以便頁面可以交互。 所以主要問題是我們必須要等待每一步完成,然后才能開始下一步。
React 團(tuán)隊(duì)正在研究一些旨在解決這些問題的新模式。
流式 SSR
瀏覽器可以通過 HTTP 流接收 HTML。流式傳輸允許 Web 服務(wù)端通過單個(gè) HTTP 連接將數(shù)據(jù)發(fā)送到客戶端,該連接可以無限期保持打開狀態(tài)。因此,我們可以通過網(wǎng)絡(luò)以多個(gè)塊的形式在瀏覽器上加載數(shù)據(jù),這些數(shù)據(jù)在渲染時(shí)按順序加載。
(1)React 18 之前的流式渲染
流式渲染并不是 React 18 中全新的東西。事實(shí)上,它從 React 16 開始就存在了。React 16 有一個(gè)名為 renderToNodeStream 的方法,與 renderToString 不同,它將前端渲染為瀏覽器的 HTTP 流。
這允許在渲染它的同時(shí)以塊的形式發(fā)送 HTML,從而為用戶提供更快的 TTFB 和 LCP,因?yàn)槌跏?HTML 更快地到達(dá)瀏覽器。
(2)React 18 中的流式 SSR
React 18 棄用了 renderToNodeStream API,取而代之的是一個(gè)名為 renderToPipeableStream 的新 API,它通過 Suspense 解鎖了一些新功能,允許將應(yīng)用分解為更小的獨(dú)立單元,這些單元可以獨(dú)立完成我們在 SSR 中看到的步驟。這是因?yàn)?Suspense 添加了兩個(gè)主要功能:
服務(wù)端流式渲染;
客戶端選擇性注水。
① 服務(wù)端流式渲染
如上所述,React 18 之前的 SSR 是一種全有或全無的方法。 首先,需要獲取頁面所需的數(shù)據(jù),并生成 HTML,然后將其發(fā)送到客戶端。 由于 HTTP 流,情況不再如此。
在 React 18 中想要使用這種方式,可以包裝可能需要較長時(shí)間才能加載且在 Suspense 中不需要立即顯示在屏幕上的組件。
為了了解它的工作原理,假設(shè) Comments API 很慢,所以我們將 Comments 組件包裝在Suspense 中:
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
這樣,初始 HTML 中就不存在 Comments,返回的只有占位的 Spinner:
<main>
<nav>
<!--NavBar -->
<a href="/">Home</a>
</nav>
<aside>
<!-- Sidebar -->
<a href="/profile">Profile</a>
</aside>
<article>
<!-- Post -->
<p>Hello world</p>
</article>
<section id="comments-spinner">
<!-- Spinner -->
<img width=400 src="spinner.gif" alt="Loading..." />
</section>
</main>
最后,當(dāng)數(shù)據(jù)準(zhǔn)備好用于服務(wù)端的 Comments 時(shí),React 將發(fā)送最少的 HTML 到帶有內(nèi)聯(lián)<script>標(biāo)簽的同一流中,以將 HTML 放在正確的位置:
<div hidden id="comments">
<!-- Comments -->
<p>First comment</p>
<p>Second comment</p>
</div>
<script>
// 簡化了實(shí)現(xiàn)
document.getElementById('sections-spinner').replaceChildren(
document.getElementById('comments')
);
</script>
因此,這解決了第一個(gè)問題,因?yàn)楝F(xiàn)在不需要等待服務(wù)端獲取所有數(shù)據(jù),瀏覽器可以開始渲染應(yīng)用的其余部分,即使某些部分尚未準(zhǔn)備好。
② 客戶端選擇性注水
即使 HTML 被流式傳輸,頁面也不會可交互的,除非頁面的整個(gè) JavaScript 被下載完。這就是選擇性注水的用武之地。
在客戶端渲染期間避免頁面上出現(xiàn)大型包的一種方法就是通過 React.lazy 進(jìn)行代碼拆分。 它指定了應(yīng)用的某個(gè)特定部分不需要同步加載,并且打包工具會將其拆分為單獨(dú)的<script>標(biāo)簽。
React.lazy 的限制是它不適用于服務(wù)端渲染。但在 React 18 中,<Suspense> 除了允許流式傳輸 HTML 之外,它還可以為應(yīng)用的其余部分注水。
所以,現(xiàn)在 React.lazy 在服務(wù)端開箱即用。 當(dāng)你將 lazy 組件包裹在 <Suspense> 中時(shí),不僅告訴 React 你希望它被流式傳輸,而且即使包裹在 <Suspense> 中的組件仍在被流式傳輸,也允許其余部分注水。這也解決了我們在傳統(tǒng)服務(wù)端渲染中看到的第二個(gè)問題。在開始注水之前,不再需要等待所有 JavaScript 下載完畢。
下面,我們把 Comments 包含在 Suspense 中,并使用新的 Suspense 架構(gòu),來看看應(yīng)用的生命周期:
<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
渲染周期如下:
流式 SSR 渲染周期.gif
這就產(chǎn)生了像下面這樣的 Network 圖:
這個(gè)例子想說明的是,對于 Suspense,很多連續(xù)發(fā)生的事情現(xiàn)在可以并行發(fā)生。
這不僅有助于我們在 HTML 被流式傳輸后更快地 TTFB,而且用戶不必等待所有 JavaScript 被下載才能開始與應(yīng)用交互。 除此之外,它還有助于在頁面開始流式傳輸時(shí)立即加載其他資源(CSS、JavaScript、字體等),有助于并行更多請求。
另外,如果有多個(gè)組件包裹在 Suspense 中并且還沒有在客戶端上注水,但是用戶開始與其中一個(gè)交互,React 將優(yōu)先考慮給該組件注水。
Server components (Alpha)
上面,我們介紹了如何通過將應(yīng)用分解為更小的單元并分別對它們進(jìn)行流式處理和選擇性注水來提高服務(wù)端渲染性能。 但是,如果有一種方法可以完全不需要對應(yīng)用的某些部分進(jìn)行注水呢?
這就是全新的 Server Components RFC 的用武之地。它旨在補(bǔ)充服務(wù)端渲染,允許擁有僅在服務(wù)端渲染且沒有交互性的組件。
它們的工作方式就是可以使用 .server.js/jsx/ts/tsx 擴(kuò)展創(chuàng)建非交互式服務(wù)端組件,然后它們可以無縫集成并將 props 傳遞給客戶端組件(使用 .client.js/jsx/ts/tsx 擴(kuò)展),它可以處理頁面的交互部分。以下是它提供的功能的:
(1)不影響客戶端包
服務(wù)端組件僅在服務(wù)端渲染,不需要注水。它允許我們在服務(wù)端渲染靜態(tài)內(nèi)容,同時(shí)對客戶端包大小沒有影響。 如果使用的是繁重的庫并且沒有交互性,這可能特別有用,并且它可以完全渲染在服務(wù)端,而不會影響客戶端包。 RFC 中的 Notes 預(yù)覽就是一個(gè)很好的例子:
// NoteWithMarkdown.js
// 在 Server Components 之前
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
function NoteWithMarkdown({text}) {
const html = sanitizeHtml(marked(text));
return (/* render */);
}
// NoteWithMarkdown.server.js - Server Component === 包大小為0
import marked from 'marked'; // 包大小為0
import sanitizeHtml from 'sanitize-html'; // 包大小為0
function NoteWithMarkdown({text}) {
const html = sanitizeHtml(marked(text));
return (/* render */);
}
(2)服務(wù)端組件不具有交互性,但可以與客戶端組件組合
由于它們只在服務(wù)端渲染,它們只是接收 props 并渲染視圖的 React 組件。 因此,它們不能像常規(guī)客戶端組件中那樣擁有狀態(tài)、effects 和事件處理程序之類的東西。
盡管它們可以導(dǎo)入具有交互性的客戶端組件,并且在客戶端上渲染時(shí)注水,正如我們在普通 SSR 中看到的那樣。 客戶端組件與服務(wù)端組件類似,使用 .client.jsx 或 .client.tsx 后綴定義。
這種可組合性使開發(fā)人員在頁面上節(jié)省大量的包大小,例如具有大部分靜態(tài)內(nèi)容和很少交互元素的詳情頁。 例如:
// Post.server.js
import { parseISO, format } from 'date-fns';
import marked from 'marked';
import sanitizeHtml from 'sanitize-html';
import Comments from '../Comments.server.jsx'
// 導(dǎo)入客戶端組件
import AddComment from '../AddComment.client.jsx';
function Post({ content, created_at, title, slug }) {
const html = sanitizeHtml(marked(content));
const formattedDate = format(parseISO(created_at), 'dd/MM/yyyy')
return (
<main>
<h1>{title}</h1>
<span>Posted on {formattedDate}</span>
{content}
<AddComment slug={slug} />
<Comments slug={slug} />
</main>
)
}
// AddComment.client.js
function AddComment({ hasUpvoted, postSlug }) {
const [comment, setComment] = useState('');
function handleCommentChange(event) {
setComment(event.target.value);
}
function handleSubmit() {
// ...
}
return (
<form onSubmit={handleSubmit}>
<textarea name="comment" onChange={handleCommentChange} value={comment}/>
<button type="submit">
Comment
</button>
</form>
)
}
上面的代碼是服務(wù)端組件如何與客戶端組件組合的示例。 讓我們來分解一下:
Post 服務(wù)端組件主要包含靜態(tài)數(shù)據(jù),包括文章標(biāo)題、內(nèi)容和發(fā)布日期。
由于服務(wù)端組件不能有任何交互性,我們導(dǎo)入了一個(gè)名為 AddComment 的客戶端組件,它允許用戶添加評論。
這里,我們在服務(wù)端組件中導(dǎo)入的所有日期和 markdown 解析庫都不會在客戶端下載。我們在客戶端下載的唯一 JavaScript 就是 AddComment 組件。
(3)服務(wù)端組件可以直接訪問后端
由于它們僅在服務(wù)端渲染,因此可以使用它們直接從組件訪問數(shù)據(jù)庫和其他僅限后端的數(shù)據(jù)源,如下所示:
// Post.server.js
import { parseISO, format } from 'date-fns';
import marked from 'marked';
import sanitizeHtml from 'sanitize-html';
import db from 'db.server';
// 導(dǎo)入客戶端組件
import Upvote from '../Upvote.client.js';
function Post({ slug }) {
// 直接從數(shù)據(jù)庫中讀取數(shù)據(jù)
const { content, created_at, title } = db.posts.get(slug);
const html = sanitizeHtml(marked(content));
const formattedDate = format(parseISO(created_at), 'dd/MM/yyyy');
return (
<main>
<h1>{title}</h1>
<span>Posted on {formattedDate}</span>
{content}
<AddComment slug={slug} />
<Comments slug={slug} />
</main>
);
}
現(xiàn)在你可能會說,在傳統(tǒng)的服務(wù)端渲染中也可以實(shí)現(xiàn)這一點(diǎn)。 例如,Next.js 可以直接在 getServerSideProps 和 getStaticProps 中訪問服務(wù)端數(shù)據(jù)。 沒錯(cuò),但區(qū)別在于,傳統(tǒng)的 SSR 是一種全有或全無的方法,只能在頂級頁面上完成,但服務(wù)端組件可以在每個(gè)組件的基礎(chǔ)上執(zhí)行此操作。
(4)自動(dòng)代碼拆分
代碼拆分是一個(gè)概念,它允許將應(yīng)用分成更小的塊,向客戶端發(fā)送更少的代碼。對應(yīng)用進(jìn)行代碼拆分的最常見方式就是按路由進(jìn)行拆分。這也是 Next.js 等框架默認(rèn)拆分包的方式。
除了自動(dòng)代碼拆分之外,React 還允許使用 React.lazy API 在運(yùn)行時(shí)延遲加載不同的模塊。 這又是一個(gè)來自 RFC 的很好的例子,說明這可能特別有用:
// PhotoRenderer.js
// 在 Server Components 之前
import React from 'react';
const OldPhotoRenderer = React.lazy(() => import('./OldPhotoRenderer.js'));
const NewPhotoRenderer = React.lazy(() => import('./NewPhotoRenderer.js'));
function Photo(props) {
if (FeatureFlags.useNewPhotoRenderer) {
return <NewPhotoRenderer {...props} />;
} else {
return <OldPhotoRenderer {...props} />;
}
}
這種技術(shù)通過在運(yùn)行時(shí)只動(dòng)態(tài)導(dǎo)入需要的組件來提高性能,但它確實(shí)有一些問題。 例如,這種方法會延遲應(yīng)用開始加載代碼的時(shí)間,從而抵消了加載更少代碼的好處。
正如我們之前在客戶端組件如何與服務(wù)器組件組合中看到的那樣,它們通過將所有客戶端組件導(dǎo)入視為潛在的代碼拆分點(diǎn),并允許開發(fā)人員選擇要在服務(wù)端更早渲染的內(nèi)容,從而使客戶端能夠更早下載。 下面是 RFC 中使用服務(wù)端組件的相同 PhotoRenderer 示例:
// PhotoRenderer.server.js - Server Component
import React from 'react';
import OldPhotoRenderer from './OldPhotoRenderer.client.js';
import NewPhotoRenderer from './NewPhotoRenderer.client.js';
function Photo(props) {
if (FeatureFlags.useNewPhotoRenderer) {
return <NewPhotoRenderer {...props} />;
} else {
return <OldPhotoRenderer {...props} />;
}
}
服務(wù)端組件可以在保留客戶端狀態(tài)的同時(shí)重新加載:我們可以隨時(shí)從客戶端重新獲取服務(wù)端樹,以從服務(wù)端獲取更新的狀態(tài),而不會破壞本地客戶端狀態(tài)、焦點(diǎn)甚至正在進(jìn)行的動(dòng)畫。
這是可能的,因?yàn)榻邮盏降?UI 描述是數(shù)據(jù)而不是純 HTML,這允許 React 將數(shù)據(jù)合并到現(xiàn)有組件中,從而使客戶端狀態(tài)不會被破壞。
(5)服務(wù)端組件與 Suspense 集成
服務(wù)器組件可以通過 <Suspense> 逐步流式傳輸,正如在上面中看到的那樣,這允許我們在等待頁面剩余部分加載時(shí)創(chuàng)建加載狀態(tài)并快速顯示重要內(nèi)容。
接下來看看上面的例子在使用 React 服務(wù)端組件時(shí)是什么樣的。 這次 Sidebar 和 Post 是服務(wù)端組件,而 Navbar 和 Comments 是客戶端組件。 我們也將 Post 包裹在 Suspense 中。
<Layout>
<NavBar />
<SidebarServerComponent />
<RightPane>
<Suspense fallback={<Spinner />}>
<PostServerComponent />
</Suspense>
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>
渲染周期如下:
Server components 渲染周期.gif
它的 Network 圖與使用 Suspense 的流式渲染非常相似,但 JavaScript 更少。因此,服務(wù)端組件甚至是解決我們一開始的問題的進(jìn)一步措施,它不僅可以下載更少的 JavaScript,而且還顯著改善了開發(fā)者體驗(yàn)。
React 團(tuán)隊(duì)還在 RFC 常見問題解答中提到,他們在 Facebook 的單個(gè)頁面上對少數(shù)用戶進(jìn)行了實(shí)驗(yàn),產(chǎn)品代碼大小減少了約 30%。
什么時(shí)候可以開始使用這些功能?
目前,服務(wù)端組件仍處于 alpha 階段,而具有新 Suspense 架構(gòu)的流式 SSR 所需的用于數(shù)據(jù)獲取的 Suspense 還沒有正式發(fā)布,將在 React 18 的小更新中發(fā)布。
相關(guān)演示
在這里查看 React 團(tuán)隊(duì)和 Next.js 團(tuán)隊(duì)的演示:
使用新的 Suspense 架構(gòu)演示流式傳輸 SSR:https://codesandbox.io/s/kind-sammet-j56ro
服務(wù)端組件演示:https://github.com/reactjs/server-components-demo
Next.js 服務(wù)端組件演示:https://github.com/vercel/next-react-server-components
參考文章:https://prateeksurana.me/blog/future-of-rendering-in-react/
作者:CUGGZ
歡迎關(guān)注微信公眾號 :前端充電寶