无废话系列之 use client ≠ client only

今儿部门新人问我:"已经在页面里写明了use client了,为什么还是报错 Hydration failed 啊?"

原因是他在代码里写了typeof window !== 'undefined'

下面我会把 为什么 use client 仍然会出现 Hydration failed 这个问题完整讲清楚,因为这是 Next.js 里非常常见的误解。

结论先说:use client ≠ 只在客户端运行

这是大家最容易误解的点。

use client 的真正含义:

"让这个组件在客户端 可以使用 React 的交互功能(如 useState/useEffect)"

组件本身仍然会在服务端执行一次来生成 HTML(SSR) ,然后再在客户端 hydration。

所以:

  • 你写了 use client
  • 但组件仍然 会在服务端运行一次(Node 环境)
  • 服务端渲染出来的 HTML 会发送给浏览器
  • 浏览器再执行相同的 React 代码进行 hydration

⚠ 因此,即使是 Client Component,它的 模块初始化代码(顶层代码)仍然会在服务器跑一次

因此:顶层使用 window 依旧会报错

你写:

ts 复制代码
if (typeof window !== 'undefined') {
  ...
}

这个语句依旧会被 服务端执行一次(因为 SSR 需要它),虽然不会崩溃,但关键问题是

SSR 输出的 HTML 与 CSR 输出的 HTML 不一致 → hydration fail

示例:

tsx 复制代码
"use client"

export default function Page() {
  let text = "hello";

  if (typeof window !== "undefined") {
    text = "hello from client";
  }

  return <div>{text}</div>;
}

服务器执行时:

  • typeof window === 'undefined'
  • text = "hello"

浏览器执行 hydration 时:

  • typeof window !== 'undefined'
  • text = "hello from client"

最终 HTML 不一致:

Server HTML Client HTML
<div>hello</div> <div>hello from client</div>

直接导致:Hydration failed

🔥 所以重点:

Client Component 依然会 SSR,因此不能在渲染逻辑里使用 window。

✔ 但可以在 useEffect 里使用,因为 effect 只在客户端执行。

推荐写法(100% 不会 Hydration failed)

如果你要用 windowdocumentlocalStorage 等:

tsx 复制代码
"use client";

import { useEffect, useState } from "react";

export default function Page() {
  const [text, setText] = useState("hello");

  useEffect(() => {
    setText("hello from client");
  }, []);

  return <div>{text}</div>;
}

✔ SSR 输出:hello

✔ 客户端 hydration 输出:hello(一致)

✔ hydration 后 useEffect 修改内容 → 不会报错

那什么时候可以用 typeof window !== 'undefined'

只能在 effect / event handler 里用

tsx 复制代码
useEffect(() => {
  if (typeof window !== 'undefined') {
    console.log(window.innerWidth);
  }
}, []);

❌ 不能用于:

  • 顶层逻辑
  • render 逻辑(JSX 里)
  • useState 初始值函数
  • 影响 SSR 输出的地方

如果真的要完全避免 SSR?

你需要 动态关闭 SSR ,而不是仅仅 use client

tsx 复制代码
const Page = dynamic(() => import("./page-client"), {
  ssr: false
});

这样组件 完全在客户端渲染,不再进行 hydration 匹配

总结(重要)

误解 正确理解
use client = only client ❌ 错
Client Component 不会在服务器执行 ❌ 会在服务器执行一次(SSR)
能直接在 JSX 或顶层使用 window ❌ 会导致 SSR/CSR 内容不一致
需要浏览器 API → 用 useEffect ✔ 正确做法
相关推荐
zlpzlpzyd8 小时前
vue.js 3中全局组件和局部组件的区别
前端·javascript·vue.js
浩星8 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css
一只小风华~8 小时前
Vue.js 核心知识点全面解析
前端·javascript·vue.js
2022.11.7始学前端8 小时前
n8n第七节 只提醒重要的待办
前端·javascript·ui·n8n
SakuraOnTheWay8 小时前
React Grab实践 | 记一次与Cursor的有趣对话
前端·cursor
阿星AI工作室8 小时前
gemini3手势互动圣诞树保姆级教程来了!附提示词
前端·人工智能
徐小夕8 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx8 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder9 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy9 小时前
Cursor 前端Global Cursor Rules
前端·cursor