当前位置: 首页 > news >正文

React水合技术:优化SSR和CSR的完美结合

讨论水合(Hydration)之前,先说下 CSR 和 SSR:

  • CSR(Client-side Rendering):在CSR中,整个应用程序的构建和渲染都发生在客户端浏览器中。当用户访问一个CSR应用时,浏览器会下载应用的JavaScript代码,然后在用户的设备上执行该代码来渲染页面。这种方式的好处是可以在客户端实现动态交互,但也有性能挑战,因为首次加载时需要下载大量的JavaScript代码,导致页面加载时间较长
  • SSR(Server-side Rendering):在SSR中,服务器在接收到客户端请求时,会在服务器上预先渲染HTML内容,并将其发送到客户端浏览器。这意味着用户会更快地看到内容,因为不必等待大量JavaScript代码下载和执行。但与CSR相比,SSR可能在复杂的应用中导致服务器负载增加,并且对实现某些交互功能有一定限制(并不总是提供更快的可交互时间)

什么是水合

水合(Hydration)在前端开发中是指将服务端生成的静态HTML转换为动态可交互网页的过程。

过程结合了服务端渲染(SSR)和客户端渲染(CSR)的优点:服务端渲染提供了更快的首屏加载时间和更好的SEO,而客户端渲染提供了更丰富的交互体验。

传统客户端渲染问题

客户端渲染 (CSR) - 初始加载时空白、SEO不友好

function CSRApp() {return (<div id="root">{/* 需要等待JS加载执行后才能看到内容 */}</div>);
}ReactDOM.render(<CSRApp />, document.getElementById('root'));
水合优势

立即展示服务端渲染的HTML

// 服务器端预渲染的 HTML
<div id="root"><h1 data-reactroot="">Hello, World!</h1><p>这是服务器渲染的内容</p>
</div>// 客户端水合 - 立即显示内容,然后添加交互性
hydrateRoot(document.getElementById('root')<CSRApp />);

水合关键过程:

  • 绑定事件处理器:使得服务器端渲染的HTML元素变得可交互。例如,按钮的点击事件、表单的提交事件等。
  • 重建应用状态:恢复或初始化客户端的JavaScript应用状态,使得客户端代码和服务器端渲染的一致。
  • 同步DOM:确保在客户端的虚拟DOM与服务器端生成的实际DOM一致。

水合的方式有很多种,下面展开介绍几种常用的「下述示例代码逻辑性没问题,但非完整」:

特性完全水合渐进式水合选择性水合流式水合
React版本所有版本React 16+React 18+React 18+
实现复杂度简单中等中等复杂
性能影响可能阻塞良好最优优秀
用户体验一般良好优秀优秀
SEO友好
学习成本

完全水合

应用一次性完成水合过程

React 18 + Node

server.js   // 同构渲染
App.jsx     // 共享组件
client.js   // 激活脚本

App.jsx

import React, { useState } from 'react';export default function App() {const [n, setN] = useState(0);return (<button onClick={() => setN(n + 1)}>已点击 {n}</button>);
}

server.js

import express from 'express';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App.jsx';const app = express();
app.use(express.static('.'));   // 把 client.js 暴露出去app.get('/', (req, res) => {// 1. 把组件渲染成“死”的 HTML 字符串const html = ReactDOMServer.renderToString(<App />);// 2. 把这段 HTML 插进页面模板,同时引入 client.jsres.send(`<!doctype html><html><body><div id="root">${html}</div><script type="module" src="/client.js"></script></body></html>`);
});app.listen(3000, () => console.log('http://localhost:3000'));

client.js

import { hydrateRoot } from 'react-dom/client';
import App from './App.jsx';// 将 React 连接到由 React 在服务端环境中渲染的现有 HTML 中
hydrateRoot(document.getElementById('root'), <App />);

验证“不重建 DOM”:点击按钮,React 的计数器 + 原生监听都会触发,说明服务端生成的 DOM 节点被完整复用,而非重新创建。

document.querySelector('button').addEventListener('click', () => console.log('原生Click事件'))

✅ 实现简单,开箱即用;SEO 友好,完整的内容预渲染
❌ 性能瓶颈明显;可交互时间(TTI)延迟("下载 → 解析 → hydrate"串行执行,遇到重组件整页都会卡顿)
适用场景: 小型应用、原型开发、对性能要求不高的项目

渐进式水合

可自定义触发条件:如可见性、时间等(优先水合关键组件,非关键组件延迟水合)。

基于 Intersection Observer 实现

import { useEffect, useRef, useState } from 'react';function useProgressiveHydration(options = {}) {const { threshold = 0.1, rootMargin = '50px' } = options;const [shouldHydrate, setShouldHydrate] = useState(false);const ref = useRef(null);useEffect(() => {const element = ref.current;if (!element || shouldHydrate) return;const observer = new IntersectionObserver(([entry]) => {if (entry.isIntersecting) {setShouldHydrate(true);observer.disconnect();}},{ threshold, rootMargin });observer.observe(element);return () => observer.disconnect();}, [shouldHydrate, threshold, rootMargin]);return [ref, shouldHydrate];
}// 使用示例
function LazyHydratedComments() {const [ref, shouldHydrate] = useProgressiveHydration({ threshold: 0.3 });return (<div ref={ref} style={{ minHeight: '200px' }}>{shouldHydrate ? (<CommentsSection />) : (<div className="comments-placeholder"><p>评论加载中...</p></div>)}</div>);
}

✅ 按需分配资源,避免主线程阻塞
❌ 实现复杂度较高,需要手动管理水合时机
适用场景: 内容型网站、博客、新闻站点

选择性水合 - React 18 核心特性

选择性水合是指在水合过程中,根据用户交互:优先水合被交互的部分,而中断或延迟其他部分的水合。

如果页面正在水合,但用户点击了某个按钮,React 会优先水合这个按钮相关的组件,以便快速响应交互。

选择性水合依赖于React 18的 hydrateRootSuspense。-- 将应用的不同部分用 Suspense 包裹,这样 React 就可以独立地水合每个 Suspense 边界。当用户与一个尚未水合的 Suspense 边界内的组件交互时,React 会优先水合这个边界。

import { Suspense, useState } from 'react';function App() {const [activeSection, setActiveSection] = useState('home');return (<div>{/* React 自动监控这些点击事件,调整水合优先级 */}<nav><button onClick={() => setActiveSection('home')}>首页</button><button onClick={() => setActiveSection('profile')}>个人资料</button><button onClick={() => setActiveSection('settings')}>设置</button></nav><main>{activeSection === 'home' && (<Suspense fallback={<div>加载中...</div>}>{/* 当用户点击"首页"时,这个组件优先水合 */}<HomeContent /></Suspense>)}{activeSection === 'profile' && (<Suspense fallback={<div>加载中...</div>}>{/* 当用户点击"个人资料"时,这个组件优先水合 */}<ProfileContent /></Suspense>)}</main></div>);
}

✅ 避免水合阻塞主线程,改善用户体验
❌ 需要React 18及以上版本,将应用划分为多个Suspense边界,这可能需要调整组件结构
适用场景: 大型交互式应用、电商网站

流式水合

流式水合指的是服务器端使用流式传输(Streaming)将渲染的HTML分块发送到客户端,客户端在接收到这些分块后逐步进行水合。

浏览器可以更早地开始渲染页面,而不必等待整个HTML文档生成完毕。特别是对于慢网络或服务器生成部分内容较慢的情况,可以显著提升首屏显示时间。

// 服务器端 - 使用流式渲染
import { renderToPipeableStream } from 'react-dom/server';app.get('/product/:id', async (req, res) => {const { id } = req.params;const { pipe } = renderToPipeableStream(<ProductPage productId={id} />,{bootstrapScripts: ['/client.js'],onShellReady() {// 1. 先发送页面外壳res.setHeader('Content-type', 'text/html');pipe(res);},onAllReady() {console.log('所有内容渲染完成');}});
});// 客户端组件
function ProductPage({ productId }) {return (<div className="product-page">{/* 立即渲染的部分 */}<Header /><Breadcrumb />{/* 流式渲染的产品信息 */}<Suspense fallback={<div className="product-skeleton"><div className="image-placeholder"></div><div className="info-placeholder"></div></div>}><ProductDetails productId={productId} /></Suspense>{/* 流式渲染的推荐商品 */}<Suspense fallback={<RecommendationSkeleton />}><ProductRecommendations productId={productId} /></Suspense>{/* 流式渲染的用户评价 */}<Suspense fallback={<ReviewsSkeleton />}><ProductReviews productId={productId} /></Suspense></div>);
}// 异步数据获取组件
async function ProductDetails({ productId }) {// 模拟数据获取const product = await fetchProduct(productId);const inventory = await fetchInventory(productId);return (<div className="product-details"><ProductImages images={product.images} /><ProductInfo product={product} /><InventoryStatus inventory={inventory} /><AddToCart product={product} /></div>);
}async function ProductRecommendations({ productId }) {const recommendations = await fetchRecommendations(productId);return <RecommendationGrid products={recommendations} />;
}async function ProductReviews({ productId }) {const reviews = await fetchReviews(productId);return <ReviewsList reviews={reviews} />;
}

✅ 最快的首屏显示时间
❌ 服务器配置复杂,错误处理复杂
适用场景: 对首屏加载速度要求极高的应用、慢网络环境、大型内容网站

一旦组件流式传输到客户端,就可以对其进行水合,因为我们不再需要等待所有 JavaScript 加载才能开始水合,并且可以在所有组件都完成水合之前开始与应用程序交互。

http://www.dtcms.com/a/477564.html

相关文章:

  • 【六级】全国大学英语六级历年真题及答案解析PDF电子版(2015-2025年6月)
  • Adware Zap - Malware Cleaner for Mac v2.12.0 轻量级广告和恶意软件清理工具
  • 从底层到上层的“外挂”:deque、stack、queue、priority_queue 全面拆解
  • 淘客网站做弹窗广告注册公司的网址是什么
  • 域名是否就是网站网站建站网站建站
  • 李宏毅机器学习笔记21
  • 自动化脚本快速批量处理
  • 哈尔滨建工建设有限公司织梦网站后台如何做百度优化
  • 第 96 场周赛:三维形体投影面积、救生艇、索引处的解码字符串、细分图中的可到达节点
  • 网站建设宁夏凤凰云什么是电子商务系统
  • 用php做电子商务网站微信做商城网站
  • 【LeetCode】146. LRU 缓存
  • Linux Cgroup与Device Whitelist详解
  • 恶意代码防范技术与原理(二)
  • Facebook广告投放:地域定向流量不精准?x个优化指南
  • 【Linux指令 (三)】从理解到熟悉:探索Linux底层逻辑与指令的高效之道,理解Linux系统理论核心概念与基础指令
  • 2025年10月实时最新获取地图边界数据方法,省市区县街道多级联动【文末附实时geoJson数据下载】
  • 基于单片机的燃气热水器智能控制系统设计
  • 江苏省建设厅网站怎么登不上html网页代码编辑器
  • 云服务器怎么架设网站wordpress删除月份归档
  • go语言返回值 于defer的特殊原理
  • 《线性代数》---大学数学基础课程
  • 【Go】---流程控制语句
  • Go小白学习路线
  • CMP (类Cloudera) CDP7.3(400次编译)在华为鲲鹏Aarch64(ARM)信创环境中的性能测试过程及命令
  • [GO]什么是热重载,如何使用Air工具
  • 福州网站建设公司哪个好济南工程建设验收公示网
  • 百度爱采购服务商查询丽水网站建设seo
  • 小黑享受思考心流: 132. 分割回文串 II
  • java求职学习day38