初識React服務端渲染——SSR

服務端渲染

Server Slide Rendering服務端渲染,又簡寫爲SSR,他一般被用在我們的SPA(Single-Page Application),即單頁應用。

服務端渲染的模式下,當用戶第一次請求頁面時,由服務器把需要的組件或頁面渲染成 HTML 字符串(在Node環境已經跑了一遍JS拿到該拿的數據),然後把它返回給客戶端。客戶端拿到手的,是可以直接渲染然後呈現給用戶的 HTML 內容,不需要爲了生成 DOM 內容自己再去跑一遍 JS 代碼。使用服務端渲染的網站,可以說是“所見即所得”,頁面上呈現的內容,我們在 html 源文件裏也能找到。

爲什麼要用SSR

更好的SEO(Search Engine Optimization)

SEO是搜索引擎優化,簡而言之就是針對百度這些搜索引擎,可以讓他們搜索到我們的應用

事實上,很多網站是出於效益的考慮才啓用服務端渲染,性能倒是在其次。
假設 A 網站頁面中有一個關鍵字叫“前端性能優化”,這個關鍵字是 JS 代碼跑過一遍後添加到 HTML 頁面中的。那麼客戶端渲染模式下,我們在搜索引擎搜索這個關鍵字,是找不到 A 網站的——搜索引擎只會查找現成的內容,不會幫你跑 JS 代碼。A 網站的運營方見此情形,感到很頭大:搜索引擎搜不出來,用戶找不到我們,誰還會用我的網站呢?爲了把“現成的內容”拿給搜索引擎看,A 網站不得不啓用服務端渲染。
但性能在其次,不代表性能不重要。服務端渲染解決了一個非常關鍵的性能問題——首屏加載速度過慢。在客戶端渲染模式下,我們除了加載 HTML,還要等渲染所需的這部分 JS 加載完,之後還得把這部分 JS 在瀏覽器上再跑一遍。

提升首屏加載速度

更好的用戶體驗,對於緩慢的網絡情況或運行緩慢的設備,加載完資源瀏覽器直接呈現,無需等待所有的 JavaScript 都完成下載並執行,才顯示服務器渲染的HTML。

客戶端渲染和服務端渲染路線對比

客戶端渲染路線:

1.請求一個html
2.服務端返回一個html
3.瀏覽器下載html裏面的js/css文件
4.等待js文件下載完成
5.等待js加載並初始化完成
6.js代碼終於可以運行,由js代碼向後端請求數據( ajax/fetch )
6.等待後端數據返回
7.react-dom( 客戶端 )從無到完整地,把數據渲染爲響應頁面

服務端渲染路線:

1.請求一個html
2.服務端請求數據( 內網請求快 )
3.服務器初始渲染(服務端性能好,較快)
4.服務端讀取瀏覽器端打包好的index.html文件爲字符串,將渲染好的組件、樣式、數據塞入html字符串,返回給瀏覽器
5.客戶端請求js/css文件
6.等待js文件下載完成
7.等待js加載並初始化完成
8.瀏覽器直接渲染接收到的html內容,並且加載打包好的瀏覽器端js文件,進行事件綁定,初始化狀態數據,完成同構

簡易的React服務端渲染

renderToString

React可以將React元素渲染成它的初始化Html,並且返回html字符串,在express服務端生成html,返回給瀏覽器渲染

const express = require('express');
const app = express();
const React = require('react');
const {renderToString} = require('react-dom/server');
const App = class extends React.PureComponent{
  render(){
    return React.createElement("h1",null,"Hello World");;
  }
};
app.get('/',function(req,res){
  const content = renderToString(React.createElement(App));
  res.send(content);
});
app.listen(3000);

同構

將上面的代碼加上JS的事件監聽服務器端渲染返給瀏覽器,你會發現在瀏覽器裏無能如何點擊都不會觸發事件
因爲renderToString只是返回html字符串,元素對應的js交互邏輯並沒有返回給瀏覽器,因此點擊h1標籤是無法響應的。

const App = class extends React.PureComponent{
  handleClick=(e)=>{
    alert(e.target.innerHTML);
  }
  render(){
    return <h1 onClick={this.handleClick}>Hello World!</h1>;
  }
};

解決方法之前,我們先講一下“同構”這個概念。何爲“同構”,簡單來說就是“同種結構的不同表現形態”。

同一份react代碼在服務端執行一遍,再在客戶端執行一遍。
同一份react代碼,在服務端執行一遍之後,我們就可以生成相應的html。在客戶端執行一遍之後就可以正常響應用戶的操作。這樣就組成了一個完整的頁面。所以我們需要額外的入口文件去打包客戶端需要的js交互代碼。
import express from 'express';
import React from 'react';
import {renderToString} from 'react-dom/server';
import App from  './src/app';
const app = express();

app.use(express.static("dist"))

app.get('/',function(req,res){
  const content = renderToString(<App/>);
  res.send(`
        <!doctype html>
        <html>
            <title>ssr</title>
            <body>
                <div id="root">${content}</div>
                <script src="/client/index.js"></script>
            </body> 
        </html>
    `);
});
app.listen(3000);

“/client/index.js”就是我們用webpack打包出來的用於客戶端執行的js文件
現在點擊會出現彈窗

ReactDOM.hydrate()

我們在服務端渲染時用ReactDOM.hydrate()來取代ReactDOM.render()
ReactDOM.render()會將掛載dom節點的所有子節點全部清空掉,再重新生成子節點。而ReactDOM.hydrate()則會複用掛載dom節點的子節點,並將其與react的virtualDom關聯上。
所以我們客戶端入口文件調整一下,拿到剛纔從後臺返回HTML裏面的root節點,進行hydrate

import React from 'react';
import {hydrate} from 'react-dom';
import App from './app';
hydrate(<App/>,document.getElementById("root"));

路由

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章