首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

React useEffect 在服务端渲染中的执行行为

  • 25-04-18 19:27
  • 3649
  • 6352
juejin.cn

React useEffect 在服务端渲染中的执行行为

本文档详细解释了 React 中 useEffect 钩子在服务端渲染 (SSR) 环境中的执行行为、原理以及最佳实践。

1. useEffect 在 SSR 中的执行行为

useEffect 钩子在服务端渲染和客户端渲染中的行为是不同的:

服务端渲染时

  • useEffect 钩子不会执行
  • 服务端渲染只执行组件的渲染函数,生成 HTML
  • 所有的副作用(包括 useEffect 中的代码)都会被跳过

客户端水合时

  • 水合完成后,useEffect 钩子会执行
  • 这是 React 将事件监听器附加到 DOM 并接管应用的过程

后续客户端渲染时

  • 每次组件重新渲染后,useEffect 都会根据其依赖项决定是否执行

2. 代码示例

javascript
代码解读
复制代码
import { useState, useEffect } from 'react'; function ExampleComponent() { const [count, setCount] = useState(0); const [serverRendered, setServerRendered] = useState(true); // 这个 useEffect 在服务端不会执行,只在客户端水合后执行一次 useEffect(() => { console.log('这个 useEffect 只在客户端执行'); setServerRendered(false); }, []); return ( <div> <p>计数: {count}p> <p>是否服务端渲染: {serverRendered ? '是' : '否'}p> <button onClick={() => setCount(count + 1)}>增加button> div> ); }

3. 为什么 useEffect 在服务端不执行?

React 设计 useEffect 不执行的原因有几个:

  1. 避免副作用:服务端渲染的目的是生成静态 HTML,不应该有副作用(如 DOM 操作、API 调用等)
  2. 避免不一致:如果服务端执行了副作用,可能会导致服务端和客户端渲染结果不一致
  3. 性能考虑:服务端渲染应该尽可能快,执行副作用会降低性能

4. 如何区分服务端和客户端代码

由于 useEffect 只在客户端执行,它常被用来处理仅客户端的逻辑:

javascript
代码解读
复制代码
import { useState, useEffect } from 'react'; function ClientOnlyComponent() { const [isClient, setIsClient] = useState(false); useEffect(() => { // 这段代码只在客户端执行 setIsClient(true); // 可以安全地使用浏览器 API document.title = '客户端渲染的页面'; }, []); // 在服务端渲染一个占位符 if (!isClient) { return <div>加载中...div>; } // 在客户端渲染实际内容 return ( <div> <h1>这是客户端渲染的内容h1> <p>窗口宽度: {window.innerWidth}pxp> div> ); }

5. 替代方案

对于需要在服务端和客户端都执行的代码,可以使用其他方法:

直接在组件函数体中

组件函数体中的代码在服务端和客户端都会执行:

javascript
代码解读
复制代码
function Component() { // 这段代码在服务端和客户端都会执行 const serverTime = new Date().toISOString(); return <div>服务器时间: {serverTime}div>; }

使用 useLayoutEffect 的替代方案

创建一个同构的 useLayoutEffect 钩子:

javascript
代码解读
复制代码
import { useEffect, useLayoutEffect } from 'react'; // 创建一个在服务端使用 useEffect,在客户端使用 useLayoutEffect 的钩子 const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; function Component() { useIsomorphicLayoutEffect(() => { // 这段代码在服务端使用 useEffect(不执行),在客户端使用 useLayoutEffect(执行) }, []); return <div>内容div>; }
详细解释

useLayoutEffect 是 React 提供的另一个副作用钩子,它与 useEffect 的主要区别在于执行时机:

  1. useEffect 执行时机:在浏览器绘制 DOM 更新后异步执行
  2. useLayoutEffect 执行时机:在 React 完成 DOM 更新后,但在浏览器绘制之前同步执行

在服务端渲染环境中,useLayoutEffect 和 useEffect 都不会执行,但 React 会发出警告,因为 useLayoutEffect 设计用于在浏览器环境中同步执行。

为了解决这个问题,我们可以创建一个同构的 useLayoutEffect 钩子,它在服务端使用 useEffect(不执行),在客户端使用 useLayoutEffect(执行)。

工作原理
  1. 环境检测:typeof window !== 'undefined' 检查代码是否在浏览器环境中运行
  2. 条件选择:
    • 在浏览器中:使用 useLayoutEffect,在 DOM 更新后同步执行
    • 在服务端:使用 useEffect,在服务端渲染时不会执行
使用场景

这种模式特别适用于需要在客户端立即执行 DOM 操作的场景,例如:

  1. 测量 DOM 元素:需要立即获取元素尺寸
  2. 动画:需要在浏览器绘制前应用动画
  3. 焦点管理:需要在 DOM 更新后立即设置焦点
完整示例
javascript
代码解读
复制代码
import { useState, useEffect, useLayoutEffect } from 'react'; // 创建同构的 useLayoutEffect const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; function AnimatedComponent() { const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const [isClient, setIsClient] = useState(false); // 使用同构的 useLayoutEffect useIsomorphicLayoutEffect(() => { // 这段代码在服务端不会执行 // 在客户端,它会在 DOM 更新后、浏览器绘制前执行 const element = document.getElementById('animated-element'); if (element) { setWidth(element.offsetWidth); setHeight(element.offsetHeight); } // 标记组件已在客户端渲染 setIsClient(true); }, []); // 在服务端渲染一个占位符 if (!isClient) { return <div id="animated-element" style={{ width: '100px', height: '100px' }}>加载中...div>; } // 在客户端渲染实际内容 return ( <div> <div id="animated-element" style={{ width: `${width}px`, height: `${height}px`, transition: 'all 0.3s ease', backgroundColor: '#f0f0f0' }} > 元素尺寸: {width}x{height} div> div> ); }
注意事项
  1. 避免水合不匹配:确保服务端和客户端渲染的内容一致,避免水合错误
  2. 性能考虑:useLayoutEffect 是同步执行的,可能会阻塞渲染,应谨慎使用
  3. 条件渲染:使用 isClient 状态来区分服务端和客户端渲染,避免使用浏览器 API 导致错误
在库中实现

如果你正在开发一个 React 库,可以在库中实现这个模式:

javascript
代码解读
复制代码
// my-react-library.js import { useEffect, useLayoutEffect } from 'react'; // 导出同构的 useLayoutEffect export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; // 使用这个钩子的组件 export function MyComponent() { useIsomorphicLayoutEffect(() => { // 客户端代码 }, []); return <div>内容div>; }

这样,库的使用者就不需要关心服务端渲染的兼容性问题,库会自动处理这些差异。

6. 常见问题和解决方案

1. 浏览器 API 不可用

在服务端渲染时,浏览器 API(如 window、document)不可用,这会导致错误。

解决方案:

javascript
代码解读
复制代码
function Component() { const [windowWidth, setWindowWidth] = useState(0); useEffect(() => { // 只在客户端执行 setWindowWidth(window.innerWidth); const handleResize = () => { setWindowWidth(window.innerWidth); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return ( <div> <p>窗口宽度: {windowWidth || '...'}pxp> div> ); }

2. 数据获取

在服务端渲染应用中,数据获取通常需要在服务端完成,然后传递给客户端。

解决方案:

javascript
代码解读
复制代码
// 服务端 async function renderApp(req, res) { // 在服务端获取数据 const data = await fetchData(); const appHtml = renderToString( <App initialData={data} /> ); res.send(` React SSR 应用
${appHtml}
`); } // 客户端 function App({ initialData }) { const [data, setData] = useState(initialData); useEffect(() => { // 只在客户端执行,用于后续数据更新 if (!initialData) { fetchData().then(result => { setData(result); }); } }, [initialData]); return ( <div> {data ? ( <ul> {data.map(item => ( <li key={item.id}>{item.name}li> ))} ul> ) : ( <p>加载中...p> )} div> ); }

7. 最佳实践

  1. 使用 useEffect 处理仅客户端的逻辑:如 DOM 操作、事件监听、浏览器 API 调用等
  2. 使用条件渲染处理服务端和客户端的差异:如使用 typeof window !== 'undefined' 检查
  3. 避免在服务端和客户端产生不同的渲染结果:这会导致水合不匹配
  4. 使用 useLayoutEffect 的替代方案:创建一个同构的 useLayoutEffect 钩子
  5. 在服务端获取数据,在客户端更新数据:避免在客户端重复获取初始数据

结论

理解 useEffect 在服务端渲染环境中的执行行为对于构建高质量的 SSR 应用至关重要。通过正确处理服务端和客户端的差异,可以创建流畅、高效的 SSR 应用,提供出色的用户体验。

注:本文转载自juejin.cn的AI程序员罗尼的文章"https://juejin.cn/post/7493798605620281356"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

104
前端
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top