ReactJS组件渲染:服务端与客户端执行步骤及核心原理咨询
React服务端渲染(SSR)核心流程与组件渲染全解析
作为React新手,刚入门时对SSR(服务端渲染)和前后端渲染分工感到困惑完全正常——我当初也花了不少时间捋清楚这些细节!下面我会一步步拆解你的问题:
一、服务端渲染时的后台流程:render代码是否在服务端执行?
是的,在SSR模式下,React的render相关代码(比如renderToString、renderToPipeableStream)是在服务端执行的,具体流程是:
- 当用户请求某个页面时,服务端首先会获取该页面所需的所有数据(比如从数据库/API拉取文章、用户信息)
- 接着服务端会创建React组件树,并把获取到的数据作为props传入组件
- 调用React提供的服务端渲染API(比如
renderToString),将组件树转换为完整的HTML字符串 - 最后把这个HTML字符串嵌入到一个基础HTML模板里(模板中会包含React客户端脚本的引用),返回给浏览器
举个简化的服务端代码示例:
import React from 'react'; import { renderToString } from 'react-dom/server'; import App from './App'; // 服务端路由处理函数 async function handleRequest(req, res) { // 1. 获取页面数据 const pageData = await fetchPageData(req.path); // 2. 渲染组件为HTML字符串 const appHtml = renderToString(<App data={pageData} />); // 3. 嵌入模板并返回 const html = ` <!DOCTYPE html> <html> <head><title>React SSR</title></head> <body> <div id="root">${appHtml}</div> <script src="/client-bundle.js"></script> </body> </html> `; res.send(html); }
二、客户端是否会将Virtual DOM与浏览器DOM合并?
准确来说,这个过程叫做hydrate(注水),而不是“合并”:
- 浏览器接收到服务端返回的HTML后,会先解析并渲染出真实的DOM节点,用户此时已经能看到静态的页面内容
- 当客户端的React脚本加载完成后,会创建对应的Virtual DOM树,然后和浏览器中已有的真实DOM进行对比
- React会复用已有的真实DOM节点,只给它们绑定事件监听器、激活交互功能,而不是重新渲染整个DOM树
需要注意:如果服务端生成的HTML和客户端Virtual DOM对应的结构不匹配,React会抛出警告,严重时可能会重新渲染部分DOM——所以要确保前后端使用的组件代码和数据是一致的。
客户端hydrate的代码示例:
import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import App from './App'; // 复用服务端生成的DOM节点,进行注水 const root = document.getElementById('root'); hydrateRoot(root, <App data={window.__INITIAL_DATA__} />);
(这里window.__INITIAL_DATA__是服务端嵌入到HTML中的初始数据,保证客户端和服务端的组件props一致)
三、如何处理浏览器兼容性问题?
React的兼容性主要从三个层面来处理:
1. React本身的版本兼容
- React 17及之前的版本支持IE11,但需要额外配置polyfills;React 18已经放弃了对IE的支持
- 如果需要兼容旧浏览器,建议使用React 17,并配合Babel和polyfill工具
2. 服务端渲染的HTML兼容性
服务端生成的是标准的HTML,只要浏览器支持基本的HTML5语法(绝大多数现代浏览器和IE9+都支持),就能正常显示静态内容,这部分几乎不需要额外处理。
3. 客户端JS的兼容性
- 语法转译:用Babel把ES6+的语法转译为ES5,确保旧浏览器能识别。比如通过
@babel/preset-env配置目标浏览器 - API补全(Polyfill):对于旧浏览器不支持的新JS API(比如
Promise、Array.prototype.includes),用core-js等工具注入polyfill。可以在项目入口文件中引入,或者通过Babel按需自动注入 - 避免服务端执行浏览器特有API:比如
window、document这些对象在服务端不存在,如果在组件的render或useMemo等服务端会执行的生命周期中调用,会报错。正确的做法是把这些代码放在useEffect(客户端才会执行)里,或者用条件判断:useEffect(() => { // 这里的代码只会在客户端执行,可以安全使用window/document console.log(window.innerWidth); }, []);
四、服务端与客户端组件渲染的具体步骤
服务端渲染步骤
- 接收请求:用户通过浏览器发送HTTP请求到服务端
- 数据预取:服务端根据请求路径,获取该页面所需的所有数据(比如调用API、查询数据库)
- 创建组件树:使用预取到的数据作为props,创建React组件实例
- 生成HTML字符串:调用服务端渲染API(如
renderToString)将组件树转换为HTML字符串 - 返回响应:把HTML字符串嵌入到基础模板中,添加客户端脚本引用,返回给浏览器
客户端Hydrate步骤
- 解析HTML:浏览器接收HTML响应,解析并渲染出真实的DOM,用户看到静态页面
- 加载客户端脚本:浏览器下载并执行React客户端bundle文件
- 初始化Virtual DOM:根据服务端传入的初始数据,创建对应的Virtual DOM树
- Hydrate绑定:将Virtual DOM与已有的真实DOM进行对比,复用DOM节点并绑定事件监听器
- 激活交互:组件进入正常的客户端生命周期,支持用户交互、状态更新等后续操作
如果是纯CSR(客户端渲染)模式,服务端只会返回一个空的HTML壳子,所有渲染工作都在客户端完成,但你问的是服务端流程,所以重点在SSR。
内容的提问来源于stack exchange,提问作者Rad'Val
相关产品推荐
相关产品推荐

