它们很接近但不等价。虽然useEffect 确实只在客户端上运行,但您在每个中都在说:
第一个例子(带钩子)
当你(下一步)构建这个组件时,给我一个带有 h1 的片段并渲染子元素。 (在构建阶段的这一点上,Next 没有运行 React,因此它将构建组件的 html。)当 React 水合后,运行这个组件。我告诉你isClient 是假的,所以返回null。然后在客户端,useEffect 运行后,你会看到isClient 是true,所以继续为用户呈现我的 html。
第二个例子(使用typeof window)
当您(下一步)构建此组件时,请检查是否定义了 window。如果是这样,请构建此 html。如果没有,“构建”一个空组件。当 React 水合后,运行这个组件。如果我们在客户端上,您会看到 window 已定义并且组件应该为用户呈现我的 html。
为什么 Next 会/不抱怨
Next 并没有抱怨您的第一个示例,因为它在构建组件时在您的 return 内的服务器上创建 html,并将其传递给客户端,并且当 React 在客户端上水合时它匹配。它在“抱怨”您的第二个示例,因为它实际上并没有在服务器上创建该 html,所以当它在 React 水合物之后在客户端上看到它时 - 瞧,警告。
记住 Next 的 build 进程和运行 next dev 之间的区别。如果你想禁用 SSR,没问题 - 只是不要在任何地方使用 getInitialProps 或 getServerSideProps。当您运行next build 时,它会将所有内容构建为静态页面,并且您将没有任何 SSR - 它只是不存在。 但是,使用您的两个示例 - 当 Next 构建此页面时,第一个将在源代码中包含原始 html (div, h1),而第二个则不会。
最后,请记住,在next dev 期间您不能真正禁用 SSR。如果您必须拥有 SSR 并且您正尝试为特定组件禁用它,那么您将在此处走一条合理的道路。但是,如果您只想要一个静态站点,则不需要像这样的解决方法来禁用 SSR - 当您构建没有 getInitialProps 或 getServerSideProps(或自定义 document 或 app 组件的站点时,它将被禁用)。
使用示例构建代码更新
在 cmets 中有一些讨论,所以我正在更新以包含一些 内置 代码,以显示实际编译的内容与 React 看到的内容。为此,我使用 OP 的代码在 Next 中创建了两个页面组件,但只是修改了 return 方法以返回一个段落标签:
// pages/sample-hook.tsx
import { useState, useEffect } from "react"
const useClientCheck = () => {
const [isClient, isClientSet] = useState(false)
useEffect(() => {
isClientSet(true)
}, [])
return isClient
}
export default function SamplePageWithHook() {
const isClient = useClientCheck()
if (!isClient) return null
return <p>Sample Page</p>
}
// pages/sample-null.tsx
export default function SamplePageWithNull() {
if (typeof window === "undefined") {
return null
}
return <p>Sample Page</p>
}
如果您查看 Next 构建的页面,您会得到以下信息:
// .next/server/pages/sample-hook.js
const useClientCheck = ()=>{
const { 0: isClient , 1: isClientSet } = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
isClientSet(true);
}, []);
return isClient;
};
function SamplePageWithHook() {
const isClient = useClientCheck();
if (!isClient) return null;
return(/*#__PURE__*/ react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx("p", {
children: "Sample Page"
}));
};
// .next/server/pages/sample-null.js
function SamplePageWithNull() {
if (true) {
return null;
}
return(/*#__PURE__*/ react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx("p", {
children: "Sample Page"
}));
};
在sample-hook.js 中,当 React 被水合时,useClientCheck 被解释并且组件根据useClientCheck 返回 html 或null。由于这个钩子,React 知道这个组件可以返回 either html 或 null。
在sample-null.js 中,不管怎样,React 解释这个组件,只看到它可以返回 null。 is(true) 之后的代码永远不会到达,因此可能的输出永远不会进入 React 树并且 React 永远不会意识到它。
这就是为什么在第二种情况下会生成警告以及为什么第一种和第二种不等价的原因。警告是告诉你 React 正在渲染 sample-null.js 中的一些东西,它以前不知道并且以前不在树中。