首页 > 其他分享 >React SSR - 写个 Demo 一学就会

React SSR - 写个 Demo 一学就会

时间:2023-06-18 19:34:34浏览次数:62  
标签:react 一学 渲染 Demo SSR React import hydrate 服务端

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="/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="/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="/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 中,可自行取阅。

标签:react,一学,渲染,Demo,SSR,React,import,hydrate,服务端
From: https://www.cnblogs.com/zxbing0066/p/17489621.html

相关文章

  • React - 07 类组件的渲染逻辑
    1.ES6类的知识ES6类的继承2.创建类组件创建类组件创建一个构造函数(类)+要求必须继承React.Component/PureComponent这个类+我们习惯于使用ES6中的class创建类「因为方便」+必须给当前类设置一个render的方法「放在其原型上」:在render方法中,返回需要渲染的......
  • 【React工作记录一百一十二】React(Hook)+TS+axios+ant design+json server实现todoli
    前言大家好我是歌谣最近开始在做关于前端扫盲的一些只是处理花了一周左右录制了了一个hook写法的关于todoList的视频主要用于基础知识的一个使用和处理目录#前端巅峰人才交流群私信我#第一节创建项目todolist项目技术选型React(Hook)+TS+axios+antdesign+jsonserve......
  • 【React工作记录一百一十三】ant design table项目中遇到的数据处理实例
    前言大家好我是歌谣今天需要进行一个数据处理的问题原始数据到目标数据的处理过程数据处理的过程就是逻辑推理的过程类似一道数学题的解法原始数据格式(本次以两组数据格式为例Rawdata)[{"id":1047,"name":"README.md","manufacture_id":1......
  • Async Await 快速Demo
    usingSystem;usingSystem.Threading;usingSystem.Threading.Tasks;namespaceAsyncAwaitDemo{classProgram{staticvoidMain(string[]args){Console.WriteLine($"a1,主线程开始,线程Id:{Thread.CurrentThread.ManagedThrea......
  • React环境搭建
    安装node.js全局安装create-react-appnpminstall-gcreate-react-app检查create-react-app版本create-react-app-V创建一个项目create-react-appmyapp临时安装create-react-app使用最新的create-react-app版本去创建项目npxcreate-react-appmyapp启动项目c......
  • react经典面试题解析--持续更新--day01
    一、类组件和函数组件的区别(面试常考)简单理解(所有同学都要掌握)1、类组件有生命周期,函数组件没有2、类组件需要继承Class,函数组件不需要3、类组件可以获取实例化的this,并且基于this做各种操作,函数组件不行4、类组件内部可以定义并维护state,函数组件都称为无状态了,那肯定......
  • Reactive Extensions 响应式扩展 用于事件驱动编程的库,具有可组合的声明性模型
    响应式扩展这个存储库包含四个库,它们在概念上是相关的,因为它们都与LINQoverofthings序列有关:ReactiveExtensionsfor.NET又名Rx.NET或Rx( System.Reactive ):一个用于事件驱动编程的库,具有可组合的声明性模型AsyncRx.NET(实验性预览)(System.Reactive.Async):Rx的实验......
  • 【React工作记录一百零八】前端小知识点扫盲笔记记录9
    前言我是歌谣放弃很容易但是坚持一定很酷微信公众号关注前端小歌谣带你进入前端巅峰交流群今天继续对前端知识的小结如何截取字符串<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge">......
  • 【React工作记录一百零九】前端小知识点扫盲笔记记录10
    前言我是歌谣放弃很容易但是坚持一定很酷微信公众号关注前端小歌谣带你进入前端巅峰交流群今天继续对前端知识的小结对称数<!DOCTYPEhtml><htmllang="en"> <head> <metacharset="UTF-8"/> <metahttp-equiv="X-UA-Compatible"content="IE=edge"/>......
  • 利用react-resizable实现antd表格头宽度可以拖拽调节
    1.创建ResizeAbleTable文件夹1.1index.jsimport{Table}from"antd4"importReact,{useEffect,useState}from"react"import{Resizable}from"react-resizable"import"./index.less"/***可伸缩列*@paramprops*@retur......