主頁 > 企業開發 > React SSR - 寫個 Demo 一學就會

React SSR - 寫個 Demo 一學就會

2023-06-19 08:17:12 企業開發

React SSR - 寫個 Demo 一學就會

今天寫個小 Demo 來從頭實作一下 reactSSR,幫助理解 SSR 是如何實作的,有什么細節,

什么是 SSR

SSRServer Side Rendering 服務端渲染,是指將網頁內容在服務器端中生成并發送到瀏覽器的技術,相比于客戶端渲染(CSR),SSR 一般用于以下場景:

  1. SEO (搜索引擎優化):由于部分搜索引擎對 CSR 內容支持不佳,所以 SSR 可以提升網站在搜索引擎結果中的排名,
  2. 首屏加載速度:由于 SSR 可以在服務器端生成完整的 HTML 頁面,用戶打開網頁時能夠更快地看到內容,不會看到長時間的白屏,可以提升用戶體驗,
  3. 隱藏某些資料:由于 CSR 需要從服務器將資料下載下來進行動態渲染,所以一些資料很容易被他人獲取,而 SSR 由于資料到渲染的程序在服務端實作,所以可以用來隱藏一些不想讓他人輕易獲得的資料,

如何實作

簡單的 SSR 其實實作很簡單,只需要在服務端匯入要渲染的組件,然后呼叫 react-dom/server 包中提供的 renderToString 方法將該組件的渲染內容輸出為字串后回傳客戶端即可,

Server 端的組件

下面寫一個簡單的例子:

服務端代碼:

import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';

import App from '../ui/App';

const app = express();

app.get('/', (_: unknown, res: express.Response) => {
    res.send(renderToString(<App />));
});

app.listen(4000, () => {
    console.log('Listening on port 4000');
});

此處要注意服務端需要支持 jsx 語法的決議,我這里直接使用 esno 執行 ts 代碼,在 tsconfig.json 中配置 jsx 即可,

其實看到這里就能明白為什么在 SSR 的頁面上使用 windowlocalstorage 等瀏覽器 API 需要放到 useEffect 里了,因為該頁面的組件都會被 server 端讀取決議,而 server 端并沒有這些 API

然后看下 App 組件的代碼:

import React, { useCallback } from 'react';

export default () => {
    const log = useCallback(() => {
        console.log('Hello world');
    }, []);

    return (
        <div>
            <p>react ssr demo</p>
            <button onClick={log}>Click me</button>
        </div>
    );
};

啟動服務器后 server 端就會使用 renderToString<App /> 渲染成 html 字串,然后通過 send 回傳給前端,下面就是服務端回傳的 html 內容:

<div>
    <p>react ssr demo</p>
    <button>Click me</button>
</div>

打開瀏覽器訪問該地址即可看到服務端回傳了該 html 片段:

picture 1

hydrate 復活組件

如果你跟著上面的操作很快就會發現問題:為什么點按鈕沒法操作了?

其實原因很簡單,因為我們只拿到了一個 html 并沒有任何的 js,事件系結等自然是無法實作的,要復活組件的互動我們還需要很重要的一步 - hydrate 也就是常說的水合,

hydrate 即通過 react 將對應的組件重新渲染到 SSR 渲染的靜態內容上,類似于 render 差異點在于 render 會忽略 root 元素中現有的 domhydrate 則會復用并會進行內容匹配檢查,

Hydration failed because the initial UI does not match what was rendered on the server.

如果遇到上述錯誤即表示在客戶端執行 hydrate 時服務端回傳的初始的 domhydrate 接收到的需要進行渲染的 dom 不匹配,

說了這么多我們再來看下代碼如何撰寫,首先要進行 hydrate 我們需要客戶端的代碼來執行:

import React from 'react';
import { hydrateRoot } from 'react-dom/client';

import App from './App';

hydrateRoot(document.getElementById('root')!, <App />);

然后將該代碼進行編譯打包,我這里就直接使用 webpack 進行打包:

const path = require('path');

module.exports = {
    entry: './ui/index.tsx',
    output: {
        path: path.resolve(__dirname, 'static'),
        filename: 'bundle.js'
    },
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.(t|j)sx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react', '@babel/preset-typescript']
                    }
                }
            }
        ]
    }
};

打包完成后生成一個 bundle.js 即可在客戶端使用它來進行 hydrate

然后我們再修改下 server 端的代碼:

app.get('/', (_: unknown, res: express.Response) => {
    res.send(
        `
<div id="root">${renderToString(<App />)}</div>
<script src="https://www.cnblogs.com/bundle.js"></script>
`
    );
});

app.use(express.static('static'));

我們在靜態內容的外層套上 root 元素,然后在下方引入我們剛剛編譯的腳本,然后就可以在客戶端看到我們想要的結果:

picture 2

可以看到事件可以正常觸發了,

此處還有個注意點,在 server 端要注意將靜態字串包裹在 root 元素中不要添加換行空格等,不然 reacthydrate 時依舊會因為內容不匹配而提示 Hydration failed(僅在 hydrateRoot 時出現,如果使用 hydrate 不會報錯,不過 18 中 hydrate 已經被棄用,)

動態資料

此時有些同學可能發現一些問題:前面的內容所渲染的內容都是靜態的,如果要針對用戶渲染出不同的內容比如用戶資訊等如何是好?

其實很簡單,只需要在服務端將對應的資訊作為 props 進行渲染即可,我們下面使用 userName 模擬一下:

app.get('/', (_: unknown, res: express.Response) => {
    const userName = ['張三', '李四', '王五', '趙六'][(Math.random() * 4) | 0];
    res.send(
        `
<div id="root">${renderToString(<App userName={userName} />)}</div>
<script src="https://www.cnblogs.com/bundle.js"></script>
`
    );
});

可是客戶端要如何與服務端匹配呢?此處有兩種解決方案:

  1. 客戶端獲取對應的資訊并在資訊獲取完成后再進行 hydrate 操作,
  2. 服務端將獲取到的資訊放在頁面中,

可以看出方案 1 會帶來明顯的延時,所以一般會采用方案 2,實作一般可以使用全域變數或特定標簽來實作:

app.get('/', (_: unknown, res: express.Response) => {
    const userName = ['張三', '李四', '王五', '趙六'][(Math.random() * 4) | 0];
    res.send(
        `
<div id="root">${renderToString(<App userName={userName} />)}</div>
<script>
window.__initialState = { userName: '${userName}' };
</script>
<script src="https://www.cnblogs.com/bundle.js"></script>
`
    );
});
import React from 'react';
import { hydrateRoot } from 'react-dom/client';

import App from './App';

hydrateRoot(document.getElementById('root')!, <App {...window.__initialState} />);

總結

  1. React 中的 SSR 可以通過 renderToString 來實作,但是只能輸出靜態內容,要讓頁面支持互動需要搭配 hydrate 使用,
  2. 實作 SSR 時服務端需要支持 jsx 語法的決議,因為服務端也需要讀取組件,
  3. hydrate 會檢查服務端與客戶端的內容是否匹配,
  4. 要實作動態資料需要在客戶端與服務端之間做好如何使用初始 props 的約定,

最后

本文的 demo 代碼放置在 React SSR Demo 中,可自行取閱,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/555516.html

標籤:其他

上一篇:前端Vue非常簡單實用商品分類展示組件 側邊商品分類組件

下一篇:返回列表

標籤雲
其他(161251) Python(38240) JavaScript(25505) Java(18246) C(15237) 區塊鏈(8271) C#(7972) AI(7469) 爪哇(7425) MySQL(7256) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4603) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2436) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1968) 功能(1967) Web開發(1951) C++(1941) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • React SSR - 寫個 Demo 一學就會

    # React SSR - 寫個 Demo 一學就會 今天寫個小 `Demo` 來從頭實作一下 `react` 的 `SSR`,幫助理解 `SSR` 是如何實作的,有什么細節。 ## 什么是 SSR `SSR` 即 `Server Side Rendering` 服務端渲染,是指將網頁內容在服務器端 ......

    uj5u.com 2023-06-19 08:17:12 more
  • 前端Vue非常簡單實用商品分類展示組件 側邊商品分類組件

    #### 前端vue非常簡單實用商品分類展示組件 側邊商品分類組件 , 下載完整代碼請訪問uni-app插件市場址:https://ext.dcloud.net.cn/plugin?id=13084 #### 效果圖如下: ![](https://p3-juejin.byteimg.com/tos- ......

    uj5u.com 2023-06-18 08:04:39 more
  • 記錄--封裝一個通過js呼叫的全域vue組件

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 在使用vue專案撰寫的時候,不可避免的會碰到需要時js api來呼叫組件進行顯示的情況 例如餓了么element ui 的 Notification 通知、Message 訊息提示等組件 雖然已經提供了,但是由于api的限制,我們只 ......

    uj5u.com 2023-06-18 08:04:33 more
  • JavaScript之Object.defineProperty()

    ## 1. 物件的定義與賦值 經常使用的定義與賦值方法`obj.prop =value`或者`obj['prop']=value` ```js let Person = {}; Person.name = "Jack"; Person["gender"] = "female"; console.lo ......

    uj5u.com 2023-06-18 08:04:26 more
  • 基于uniapp+vite4+vue3搭建跨端專案|uni-app+uview-plus模板

    最近得空學習了下uniapp結合vue3搭建跨端專案。之前也有使用uniapp開發過幾款聊天/仿抖音/后臺管理等專案,但都是基于vue2開發。隨著vite.js破局出圈,越來越多的專案偏向于vue3開發,就想著uniapp搭配vite4.x構建專案效果會如何?經過一番嘗試果然真香~ 版本資訊 HBu ......

    uj5u.com 2023-06-18 07:58:51 more
  • JavaScript之Object.defineProperty()

    ## 1. 物件的定義與賦值 經常使用的定義與賦值方法`obj.prop =value`或者`obj['prop']=value` ```js let Person = {}; Person.name = "Jack"; Person["gender"] = "female"; console.lo ......

    uj5u.com 2023-06-18 07:58:21 more
  • 記錄--設計一個可選擇不連續的時間范圍的日期選擇器

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 npm包:sta-datepicker 效果圖 需求 普通的時間選擇器要么只能單選,要么只能選范圍,不可以隨意選擇若干個時間,同時大多數現成的時間選擇器選擇結束會收起來,很不方便。現在需求如下 1、可以自己控制展開收起 2、可以選擇不連續 ......

    uj5u.com 2023-06-17 08:08:47 more
  • javaScript基礎語法之正則運算式

    ##正則運算式規則 | 運算式 | 描述 | | | | | [0-9] | 查找任何從 0 至 9 的數字。例如,匹配: '111' ,不匹配:'eee' | | [abc] | 查找方括號之間的任何字符。例如,/[123]/ 匹配 "1234567" 中的 "123","1" 中的 "1"。但是 ......

    uj5u.com 2023-06-17 08:08:40 more
  • TS中, Array.reduce提示沒有與此呼叫匹配的多載?

    起因 一個feature開發, 結果需求評審、工時預估, 簡直是事故級別的. 最后, 迫于無奈, 全組人都得上去救火... 今天, 幫忙改bug的時候, 發現新checkout下來的代碼, 還帶著新鮮的語法錯誤...簡直大無語. 翻了遍代碼, 發現很多地方都存在Array.reduce型別多載相關的 ......

    uj5u.com 2023-06-17 08:08:36 more
  • Custom directive is missing corresponding SSR transform and

    ## 背景 最近在給業務組件庫集成指令庫,將各個專案中常用的指令如一鍵復制、元素和彈窗拖拽等封裝到一起,進行統一發版維護。 業務組件庫專案架構采用的是pnpm+vite+vue3+vitepress,其中vitepress主要做組件庫檔案站點同時展示可互動的組件。 ## 問題 開發運行時指令庫dem ......

    uj5u.com 2023-06-17 08:08:31 more