React 16 服務端渲染的新特性
React 16 中關於服務端渲染的新特性
快速介紹React 16 服務端渲染的新特性,包括數組、性能、流等
React 16 終於來了!🎉🎉🎉
React 16 中有許多令人激動的新特性(最著名的是Fiber的重寫),但是對我個人而言,最興奮的還是React 16 對服務器端渲染所做的許多改進。
讓我們深入了解一下在React 16 中使用新的、不同的SSR,我希望你能像我一樣興奮!
如何在React 15 中運行SSR
首先,讓我們復習一下如何在React 15 中使用SSR。為了實現SSR,通常需要運行一個基於Node的web服務器,例如Express、Hapi或Koa,可以調用renderToString
方法將根組件渲染為字符串,然后寫入響應:
// using Express
import { **renderToString** } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>");
**res.write(renderToString(<MyPage/>));**
res.write("</div></body></html>");
res.end();
});
然后,在客戶端啟動代碼中,通知客戶端使用render()
渲染在服務端生成的HTML,這與客戶端渲染應用程序的方法一致:
import { render } from "react-dom"
import MyPage from "./MyPage"
render(<MyPage/>, document.getElementById("content"));
如果你的做法正確,客戶端渲染器可以使用現有的服務器生成的HTML進行渲染,不需要更新DOM。
那么,在React 16 中,如何實現SSR呢?
React 16 向后兼容
React小組深刻承諾向后兼容,所以如果你的代碼在React 15 中運行沒有任何問題,那么,在React 16 仍然可正常運行。上一小節中的示例代碼在React 15 和 React 16 中都可以正常運行。
萬一在你的應用程序中使用React 16 卻發現問題,請提交issue!將有助於核心團隊清除React 16 版本的缺陷。
render() 變成 hydrate()
如果你將SSR從React 15 升級到React 16,在瀏覽器中將會看見如下警告:
這是一個有益的React警告。render() 已變成 hydrate()。
在React 16中,有兩種不同的方法實現客戶端渲染:render()
僅用於渲染客戶端內容,hydrate
用於渲染服務器端標記。由於React是向下兼容的,在React 16中使用render()
渲染服務端生成的標記仍舊有效,但是需要使用hydrate()
方法來消除警告,為React 17做好准備。前文的代碼片段應改寫為:
import { **hydrate** } from "react-dom"
import MyPage from "./MyPage"
**hydrate**(<MyPage/>, document.getElementById("content"));
React 16 可以處理數組、字符串和數字
在React 15中,組件的render
方法必須返回一個簡單的React元素。而在React 16中,客戶端渲染的render
方法允許組件返回字符串、數字或一組元素組成的數組。顯然,React 16服務端渲染方法hydrate
方法也支持該特性。
因此,可以使用如下方式編寫組件:
class MyArrayComponent extends React.Component {
render() {
return [
<div key="1">first element</div>,
<div key="2">second element</div>
];
}
}
class MyStringComponent extends React.Component {
render() {
return "hey there";
}
}
class MyNumberComponent extends React.Component {
render() {
return 2;
}
}
甚至可以在頂層renderToString
方法中傳入字符串、數字、組件數組:
res.write(renderToString([
<div key="1">first element</div>,
<div key="2">second element</div>
]));
// it’s not entirely clear why you would do this, but it works!
res.write(renderToString("hey there"));
res.write(renderToString(2));
這種方式有助於消除用於包裹React組件的div
或span
,有助於減小HTML文件體積。
React 16 生成更有效的HTML
說到減小HTML文件體積,React 16也從根本上減小SSR在創建HTML上的開銷。在React 15中,SSR文件中的每個HTML元素都有一個data-reactid
屬性,其值即是簡單的遞增的ID,text節點也含有react-text
和ID。查看一下代碼片段看效果:
renderToString(
<div>
This is some <span>server-generated</span> <span>HTML.</span>
</div>
);
在React 15中,該片段生成的HTML如下(注釋便於閱讀):
<div data-reactroot="" data-reactid="1"
data-react-checksum="122239856">
<!-- react-text: 2 -->This is some <!-- /react-text -->
<span data-reactid="3">server-generated</span>
<!-- react-text: 4--> <!-- /react-text -->
<span data-reactid="5">HTML.</span>
</div>
而在React 16中,所有的ID都從節點中移除了,HTML看起來簡單很多:
<div data-reactroot="">
This is some <span>server-generated</span> <span>HTML.</span>
</div>
不僅簡潔便於閱讀,而且顯著地減小HTML文件體積。棒!
React 16 允許使用非標准DOM屬性
在React 15中,DOM渲染嚴格限制HTML元素,並且移除非標准HTML屬性。而在React 16中,客戶端和服務端渲染均允許在HTML元素上使用非標准屬性。了解更多該特性的內容,請查閱 Dan Abramov’s post on the React blog
React 16 SSR不支持的錯誤邊界和Portal
React 16 客戶端渲染有兩個新特性是服務端不支持的:錯誤邊界和Portal。了解更多內容請查詢Dan Abramovd 的一篇文章 excellent post on the React blog,但是至少必須了解的是服務端不會捕獲錯誤邊界。關於Portal我目前沒有查到相應的解釋性的文章,但是Portal 的 API依賴DOM節點,因此無法在服務端使用。
React 16 執行不太嚴格的客戶端檢查
在React 15中,當重新渲染節點時,ReactDOM.render()
方法執行與服務端生成的字符挨個比對。如果一旦有不匹配的,不論什么原因,React在開發模式下會發出警告,替換整個服務端的節點數。
在React 16中,客戶端渲染使用差異算法檢查服務端生成的節點的准確性。相比於React 15更寬松;例如,不要求服務端生成的節點屬性與客戶端順序完全一致。當React 16的客戶端渲染器檢測到節點不匹配,僅僅是嘗試修改不匹配的HTML子樹,而不是修改整個HTML樹。
通常,這種變化對用戶不會有影響,調用**ReactDOM.render()/hydrate()**
時React 16不會修改SSR生成的不匹配HTML。這一項性能優化意味着你需要額外確保修復在開發
模式下的所有警告。
React 16 不需要通過編譯獲得最佳性能
在React 15中,如果直接使用SSR,即使在生產
模式下性能也不是最優的。這是因為有大量的開發人員警告和提示,並且每個警告都類似於:
if (process.env.NODE_ENV !== "production") {
// check some stuff and output great developer
// warnings here.
}
不幸的是,process.env不是一個普通的JavaScript對象,從中取值非常消耗性能。因此即使NODE_ENV
被設置為production
,僅僅檢測環境變量常常增加了大量的服務器渲染時間。
為了解決React 15中的這種問題,你需要編譯SSR代碼移除process.env
,可以使用Webpack Environment Plugin或Babeltransform-inline-environment-variables 插件。從經驗來看,許多開發同學未編譯服務端代碼,結果SSR性能明顯下降。
在React 16中,該問題已解。在React 16中只會在開始時調用一次process.env.NODE_ENV
,因此不需要編譯SSR代碼就可以獲得最佳性能。
React 16更快
說到性能,使用React做服務端渲染的同學經常抱怨說即使使用最佳實踐,大文件渲染依舊緩慢。
因此,我非常高興地公布性能測試報告,不同Node版本上React 16性能均有大幅提升:
React 16服務端渲染比React 15快。
與React 15相比,process.env
編譯后,在Node 4上大約提升2.4倍,Node 6中提升3倍,Node8.4 release版本提升3.8倍。對比未編譯的情況,React 16大幅提升性能。
為什么React 16服務端渲染比React 15快這么多?在React 15中,服務端和客戶端渲染基本是相同的代碼。意味着數據結構需要維持一個虛擬DOM,盡管調用renderToString
后vDOM很快被廢棄。也就是說服務端渲染非常浪費。
在React 16,核心團隊重新編寫服務端渲染引擎,不會創建vDOM,因此會快很多。
警告:我的測試是通過生成巨大的DOM樹,使用一個非常簡單的遞歸響應組件。這意味着它是一個非常綜合的基准,幾乎肯定不能反映真實的使用情況。如果你的組件中有一大堆復雜的“渲染”方法占用了大量的CPU周期,那么React 16可能沒那么快。所以,我絕對希望看到React 16 SSR得到明顯改善,真實的應用可能改進不到3倍。據傳,我聽過一些早期采用者的看法關於 1.3x 性能提升。在你的應用程序中測試實驗並找出最好的方法!
React 16 支持流
最后但並非最不重要的是,React 16現在支持直接渲染節點流。
渲染流可以減小第一個字節(TTFB)渲染時間,在文檔的下一個部分生成之前,將文檔的開頭向下發送到瀏覽器。所有主流瀏覽器都會在服務器以這種方式流出內容時開始解析和呈現文檔。
從呈現流中獲得的另一個很棒的東西是響應backpressure的能力。這意味着,在實踐中如果網絡支持,不能接受更多的字節,渲染得到的信號與停頓渲染到堵塞清理。這意味着服務器使用更少的內存,對I/O條件更敏感,這兩種情況都可以幫助服務器在充滿挑戰的條件下保持正常工作。
使用React 16渲染流,需要調用兩個方法:renderToNodeStream
或 renderToStaticNodeStream
,與renderToString
和 renderToStaticMarkup
相對應。新方法返回Readable。
當收到renderTo(Static)NodeStream
方法返回Readable
流,它處於暫停模式,並且還沒有渲染。當調用read或pipeWritable時開始渲染,大部分Node web框架從Writable
繼承響應對象,因此,一般來說,只要將Readable
發送到響應。
舉個例子,從上面的例子可以用流式重寫:
// using Express
import { **renderToNodeStream** } from "react-dom/server"
import MyPage from "./MyPage"
app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='content'>"); **const stream = renderToNodeStream(<MyPage/>);
stream.pipe(res, { end: false });
stream.on('end', () => {**
res.write("</div></body></html>");
res.end();
**});**
});
注意,當我們管的響應對象,我們必須包括可選參數 { end: false }
告訴流不自動結束響應當渲染完成。這允許我們完成HTML主體,並在流完全寫入響應后結束響應。
流有一些陷阱
雖然在大多數場景中,對流的渲染應該是一種升級,但目前有一些流媒體模式不能很好地工作。
一般來說,任何使用服務器呈現模式的模式都會產生標記,需要將這些標記添加到文檔中,然后才可以與流媒體基本上不兼容。其中一些示例是動態決定在前面添加到頁面中的CSS的框架向文檔添加元素的標記或框架
。如果你使用這些類型的框架,可能不得不使用字符串渲染。
另一種尚未在React 16中發揮作用的模式是嵌入調用renderToNodeStream
。在React 15是相當典型的使用rendertostaticmarkup
生成的頁面模板和嵌入調用rendertostring
產生動態的內容,如:
res.write("<!DOCTYPE html>");
res.write(renderToStaticMarkup(
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="content">
{ renderToString(<MyPage/>) }
</div>
</body>
</html>);
但是,如果用流式對等體替換這些呈現調用,該代碼將停止工作,因為它是不可能的一Readable
流(rendertonodestream
返回的)是嵌入在一個組件的元件。我希望在后續會增加!
小結
以上這些是React 16中主要的SSR變化,我希望你們和我一樣興奮。
在結束之前,我要向反應核心團隊的所有成員表示衷心的感謝,感謝他們致力於使服務器端成為反應生態系統的第一部分。該團隊包括(但絕不限於)Jim Sproch, Sophie Alpert, Tom Occhino, Sebastian Markbåge, Dan Abramov, Dominic Gannaway。謝謝,謝謝,謝謝!
現在讓我們開始使用服務器渲染一些HTML!
感謝Sunil Pai, Dan Abramov, Alec Flett, Swarup Karavadi, Helen Weng_, _ Dan Fabulich 檢閱這篇文章。
附:我在9月23日在React Boston發表該話題的演講,以及關於React 16 服務端渲染的下一步計划的思考。如果你想看,這是存檔事件直播here。如果您想跳過本文中的內容,只關注SSR的未來想法,可點擊here
在生產環境,請確保設置NODE_ENV
為 production
。
Thanks to Dan Fabulich, Alec Flett, and Helen.
感謝Dan Fabulich, Alec Flett 和 Helen。
掌聲表明你是多么欣賞Sasha Aickin的故事。
Sasha Aickin
Ex-CTO @ Redfin. (Former?) documentary filmmaker. Avid cook/book club hoster.
本文轉載自:眾成翻譯
譯者:yinqiao
鏈接:http://www.zcfy.cc/article/4353
原文:https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67