怎么给HTML“注水”,搞懂SSR的灵魂-Hydration

65 阅读3分钟

引言

我们使用服务端渲染通常是出于这几个原因:

  1. 更快的首屏加载:用户立即看到内容,无需等待JS加载完成
  2. 更好的SEO:搜索引擎可以直接索引服务端渲染的内容
  3. 平滑的交互体验:从静态到交互式的过渡无缝

在实际生产环境中,服务端渲染框架Next.js/nuxt.js提供了完整、稳定、高效的解决方案,能够显著提升开发效率和项目质量。

但是只有看清真相,才能获取真正的自由

所以,今天我们先来了解下SSR的核心原理-Hydration,相信通过下面简洁明了的内容一定让你看完后惊呼:“欧!!!原来服务端渲染(SSR)是这么回事儿啊!!!”

什么是Hydration(水合)

服务器会根据浏览器URL找到对应路径渲染的 HTML模版,文件下发给客户端。虽然HTML模版具备页面内容,但是并不包含任何动态交互逻辑,比如dom元素点击事件、数据响应式变化。 这里就有“大聪明”要问了,为啥服务器只能生成静态模板,就不能一次性搞定吗?(我曾经就是这样的“大聪明”)

那是因为服务器运行在Node.js环境中:

  • 没有DOM(document、window等)
  • 没有事件系统(click、hover等)
  • 没有CSS样式计算
  • 没有用户交互能力

即使这样,模板文件中可以引用一个脚本文件,后面浏览器会下载执行这份 HTML的JS 脚本。通过某种方式将将事件监听、数据状态、生命周期追加到现有的DOM元素上,是的,这里的某种方式就是Hydration(水合)。

看到这可能在想“这是在啰里吧嗦说些什么啊”,接下来我们结合下实际的案例看看吧。

我们先定义下服务器文件

import React from 'react'
import ReactDOM from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import App from '../app.jsx'

const express = require('express');
const server = express();
// 关键:静态资源目录,client_bundle.js就放在这里
server.use(express.static("build"));
//在‘/’路由下返回模板资源
server.get('/', (req, res) => {
    const AppHtmlString = ReactDOM.renderToString(
        <StaticRouter location={req.url}>
            <App /> //这是一个APP组件:服务器和客户端都引用它,
                    //这样确保客户端代码检查现有的DOM结构与服务端渲染的HTML匹配,才能进行Hydration
        </StaticRouter>
    )
    
    res.send(`
        <!DOCTYPE html>
        <html lang="en">
        <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        </head>
        <body>
        //服务端渲染的静态内容
        <div id="root">${AppHtmlString}</div>
        //关键:客户端脚本,执行Hydration
        <script src="/client/client_bundle.js"></script>
        </body>
        </html>
        `);
});

server.listen(3000, () => {
    console.log('Server is running on port 3000');
})

这样你在浏览器看到的就是下面图片内容:

image.png

那么这里的脚本文件client_bundle.js是啥呢?

import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from '../app.jsx'
//客户端hydrate
ReactDOM.hydrateRoot(document.getElementById('root'), (
    <BrowserRouter>
        <App /> //注意了:客户端代码检查现有的DOM结构与服务端渲染的HTML匹配,才能进行Hydration
    </BrowserRouter>
))

通过前面的步骤我们就实现了SSR的核心功能:客户端接管服务端渲染的静态HTML,使其变为可交互的应用。那咱们这个demo和nextjs相比还差了什么呢?

特性当前项目(核心原理)Next.js(完整实现)
SSR渲染✅ 手动实现✅ 自动处理
水合机制✅ 手动实现✅ 自动优化
路由同步✅ 基础实现✅ 文件系统路由
数据获取❌ 需要手动✅ 内置API
代码分割❌ 需要手动✅ 自动优化
API路由❌手动创建✅内置支持
开发体验❌ 基础✅ 优秀

ok,SSR的原理Hydration就先讲到这里了,如果有错误纰漏还请指出。咱们下回再聊聊SSR框架nextjs!