React Router 按需加載+服務器渲染的閃屏問題


伴隨着React協議的『妥協』(v16采用MIT),React為項目的主體,這個在短期內是不會改變的了,在平時使用過程中發現了如下這個問題:

在服務器渲染的時候,刷新頁面會出現閃屏的現象(白屏一閃而過)

作為努力最求極致的我,是不能容忍的,而這一現象是半道出現的,也就是在添加按需加載之后。要說清楚這個問題,得從React的服務器渲染開始說起,(急於尋求問題解決方案的,可以直接去文章后半部分)

服務器渲染(SSR)基礎原理

React的虛擬DOM是其可被用於服務端渲染的關鍵。其原理簡單的來說就是首先每個ReactComponent 在虛擬DOM中完成渲染,然后React通過虛擬DOM來更新瀏覽器DOM中產生變化的那一部分。虛擬DOM作為內存中的DOM表現,為React在Node.js這類非瀏覽器環境下提供了可能。React可以從虛擬DOM中生成一個字符串,而不是更新真正的DOM,這使得我們可以在客戶端和服務端使用同一個React Component。

基本設計理念

React 提供了兩個可用於服務端渲染組件的函數:ReactDOMServer.renderToStringReactDOMServer.renderToStaticMarkup。 在設計用於服務端渲染的ReactComponent時需要有預見性,考慮以下方面:

  • 選取最優的渲染函數。

  • 如何支持組件的異步狀態。

  • 如何將應用的初始化狀態傳遞到客戶端。

  • 哪些生命周期函數可以用於服務端的渲染。

  • 如何為應用提供同構路由支持。

  • 單例、實例以及上下文的用法。

渲染函數

render()

我們常見的render方法,用於瀏覽器渲染。

import ReactDOM from 'react-dom'; ReactDOM.render( element, container, [callback] )

renderToString()

ReactDOMServer.renderToString是兩個服務端渲染函數中的一個,也是開發主要使用的一個函數,ReactDOM.render不同,該函數去掉了用於表示渲染位置的參數。取而代之,該函數只返回一個字符串,這是一個快速的同步(阻塞式)函數,非常快。

  • 用法:

const ReactDOMServer = require('react-dom/server'); ReactDOMServer.renderToString(element)
  • 例子:

const ReactDOMServer = require('react-dom/server'); const Hello = React.createClass({ render: function() {   return <div>hello</div>; } }); const helloString = ReactDOMServer.renderToString( React.createElement(Hello) ); /* 輸出結果大概為: helloString = ` <div  data-reactid=".xxx"  data-react-checksum="-123456" >  hello </div> ` */

從上面這個例子,很容易發現,React為div注入了一些自定義屬性,首先reactid,這是在瀏覽器環境下,React為了區分DOM節點,在需要更新的時候能夠精確定位的標記。而后checksum這個屬性僅僅存在與服務端,這個你可能沒見過,或沒留意,它的作用是拿服務端返回的String與已創建的DOM做校驗,這就准許了React在客戶端和服務端在結構上擁有相同的DOM結構,該屬性只會添加在根節點元素上。拿到checksum大抵會做一些事情:

  • 檢查第一個元素是否有data-react-checksum屬性,如果有則通過ReactDOMServer.renderToString拿到前端的,通過adler32算法得到的值和data-react-checksum對比,如果一致則表示,無需渲染,否則重新渲染。

renderToStaticMarkup()

import ReactDOMServer from 'react-dom/server'; ReactDOMServer.renderToStaticMarkup(element) // eg: ReactDOMServer.renderToStaticMarkup( React.createElement(   Provider,   { store },   React.createElement(     RouterContext,     matchedData[1]   ) ) );

這個函數和上面的那個函數大體相同,除卻,它不會給節點添加任何額外的屬性值,它的返回值是『干凈』的,在某種情況下,可以節約空間使用。

選擇

每個渲染函數都有自己的用途,所以你必須明確自己的需求,再去決定使用哪個渲染函數。當且僅當你不打算在客戶端渲染這個React Component時,才應該選擇使用ReactDOMServer.renderToStaticMarkup函數。下面有一些示例:

  • 生成HTML電子郵件

  • 通過HTML到PDF的轉化來生成PDF

  • 組件測試

  • 等一些需要『純』DOM的情況下

大多數情況下,我們都會選擇使用ReactDOMServer.renderToString。這將准許React使用data-react-checksum在客戶端迅速的初始化同一個React Component,因為React可以重用服務端提供的DOM,所以它可以跳過生成DOM節點以及把他們掛載到文檔中這兩個昂貴的進程,對於復雜些的站點,這樣做就會顯著的減少加載時間,用戶可以更快的與站點進行交互。確保React Component能夠在服務端和客戶端准確的渲染出一致的結構是很重要的。如果data-react-checksum不匹配,React會舍棄服務端提供的DOM,然后生成新的DOM節點,並且將它們更新到文檔中。此時,也就不具備服務端渲染帶來的各種性能上的優勢。這個錯誤會是下面這樣的,如果你開了React dev模式:

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:
 (client) <noscript data-reacti
 (server) <div data-reactid=".q

而解決這個問題,則是保證在服務器環境下能夠渲染出與客戶端一樣的DOM結構,總結一下一般會是兩種情況:

  • 異步/延遲加載

  • 存在隨機邏輯

  • 最大的問題是,客戶端和服務端的環境差異造就的問題,如document環境下的一些元素無法在服務端渲染等

總結

上面講了些服務器渲染需要注意到的點,而一開始提到的按需加載刷新頁面出現的閃屏問題還沒有落實。以下:在異步加載的時候服務器渲染和客戶端渲染的結果不同,即通過checksum校驗失敗,重新渲染,在這個重新渲染的時候,需要重新匹配路由,以上就會出現閃屏的情況。我們注意到,在使用react-router的時候,服務器中使用到了一個match函數

match({ history, routes: getRoutes(store), location: req.originalUrl }, (error, redirectLocation, renderProps) => {})

簡單的說他匹配分析了客戶端的路由,使得渲染過程中能夠吻合到客戶端的路由控制器。而如果客戶端在首次出現需要重新渲染的時候,如果是動態路由(按需加載使用到的一項技術),就需要重新匹配渲染,這時候會出現短暫的白屏閃過。解決這個問題,只需要在客戶端渲染之前先匹配路由,使用match。(關於match的詳細介紹,參見官方文檔)

// +redux
// 客戶端在渲染前加上匹配路由函數match match({ history, routes }, (error, redirectLocation, renderProps) => { if (!error) {   // 渲染   ReactDOM.render(     <Provider store={store}>{routes}</Provider>,     document.getElementById('root')   ); } else {   console.error(error);   // todo: 錯誤信息收集 } });

以上,便可解決題中問題。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM