reactjs - React Next.js - 关于 SSR 模式的三个相关问题
问题描述
以下关于 next.js 的使用和反应以在彼此之上构建 SSR 的问题,所以我想我会把它写成一篇文章。我的主要问题是第三个问题,但我觉得我需要先了解前两个问题才能到达那里。所以我们开始:
1.客户端收到后总是从头开始重新执行整个页面,我说的对吗?
考虑这个 next.js 页面组件:
const Page = () => {
const [state, setState] = useState(getState());
function getState() {
console.log("compute initial state");
return 1;
}
return <>{state}</>;
};
据我所知,getState()
在服务器和客户端上都执行。如果我只想在服务器上执行该计算,我必须通过getInitialProps()
resp 来完成。getServersideProps()
, 对?
2. 如果预渲染的文档立即被丢弃,它对客户有什么价值?
将第一个问题更进一步,如果预呈现的文档事件无论如何都要从头开始重新计算,为什么还要将它交给客户端。客户获得初始文件有什么好处?好的,如果客户端根本无法执行js,他们至少有一些东西。但仅此而已吗?
3. 这是否意味着我必须在客户端“双重渲染”某些组件?
假设我的部分代码依赖于window
并且不能在服务器上执行。正如我发现的不同文章中所解释的那样,如果我依赖typeof window === "undefined"
代码中的检查,它可能会导致问题(并做出反应警告)。相反,我认为更好的方法是在第一次渲染后执行这些功能useEffect
:
const Page = () => {
const [value, setValue] = useState();
// Effect will be executed after the first render, e.g. never on the server
useEffect(() => {
const value = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(value)
}, []);
return (
<>
{!value && <h1>value pending ...</h1>}
{value && <h1>{value}</h1>}
</>
);
};
现在,使用这种模式,从 SSR 的角度来看,应用程序很好。但是我正在引入在客户端已经完成的额外工作:即使window
是在第一次渲染时定义的,它也只能在第二次渲染时使用。
对于这个较小的示例,这并没有起到很大的作用,但在较大的应用程序中,可能会出现闪烁,因为不必要的第一次渲染会在几毫秒后更新。此外,代码变得更难阅读并且更容易出错。
我想要的解决方案与上面的第一个问题有关:我可以以某种方式避免应用程序从头开始,而是直接从第二个渲染开始?到目前为止我错过了某种模式吗?
注意:当然我可以重写组件来简单地检查是否window
已定义:
return (
<>
<h1>{window ? window.innerWidth : "value pending ..."}</h1>
</>
);
但是,这将导致反应警告并导致我在上面喜欢的文章中描述的其他问题。另请参阅反应文档:
React 期望服务器和客户端之间呈现的内容是相同的。它可以修补文本内容的差异,但您应该将不匹配视为错误并修复它们。
非常感谢您的帮助!
解决方案
客户收到后,整个页面总是从头开始重新执行,我是对的吗?
是和不是。初始 html 在服务器上构建并作为 html 发送到客户端,然后 react 被水合,以便您的页面变得可交互。例如,考虑这个“页面”:
export default function MyPage() {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
setGreeting("Goodbye")
}, [])
return (<p>{greeting}</p>)
}
当页面在服务器上呈现时,它将呈现为 html 并将该 html 发送到客户端。因此,如果您检查页面的源代码,您会看到:
<p>Hello</p>
然后,在客户端,react 被水useEffect
合并运行,所以在浏览器中你会看到一个带有单词“Goodbye”的段落。
无论您使用的是 SSR(在服务器上按需创建 html)还是 SSG(在构建时将 html 创建到静态 html 页面中),这都是正确的。
如果预渲染的文档立即被丢弃,它对客户有什么价值?
如果您看到我的第一点,则不会丢弃整个文档。SSR 中的值是如果您希望初始问候文本不是“Hello”。例如,如果您希望服务器解析身份验证令牌、获取用户配置文件并greeting
在页面加载时使用“Hello Jim”作为种子,那么您更喜欢 SSR。如果在将 html 发送到客户端之前没有任何服务器端处理,则可以选择 SSG。
考虑这两个“页面”:
// Using SSR
export default function MyPage({customerName}) {
return (<p>{customerName}</p>)
}
// Using SSG
export default function MyPage({customerName}) {
const [greeting, setGreeting] = React.useState("Hello")
React.useEffect(() => {
// Call server to get the customer's name
const name = myApi.get('/name')
setGreeting(`Hello ${name}`)
}, [])
return (<p>{greeting}</p>)
}
在第一个示例中,服务器p
使用客户名称(来自服务器上的某个进程)呈现标签,因此 html 源代码将包含该客户的名称。这里什么都没有扔掉。
在第二个示例中,站点构建为 html,源代码的p
标签显示为“Hello”。当页面被访问时,useEffect
运行并p
在您的 api 响应时更新标签。所以用户会在 x 微秒内看到“Hello”,然后它会切换到“Hello Jim”。
这是否意味着我不必在客户端“双重渲染”某些组件?
不 - 您可以控制在服务器上呈现的内容与在客户端上呈现的内容。如果您在服务器上使用数据为组件播种并且不在客户端上更改它,则它不会重新呈现。
在你的例子中,是的 - 你是双重渲染。但你可能不需要。如您所述,window
客户端上不存在。如果您绝对必须在获得窗口大小之前向您的用户显示“值待定...”行,那么您将进行双重渲染 - 一次在服务器上填充该字符串,然后一次在反应水合物时替换它客户端。
如果您不需要显示该挂起的行,而只需要在window
实际存在时在客户端上显示该值,则可以像这样重写它:
export default function Page() {
const [value, setValue] = React.useState();
React.useEffect(() => {
const newValue = window.innerWidth; // Some computations or subscriptions that depend on window
setValue(newValue)
}, []);
if(value) {
return <h1>{value}</h1>
}
return null
};
在这种情况下,服务器上没有渲染任何内容,因为服务器上没有value
。仅当客户端处于水合状态时,才会useEffect
运行、更新value
和渲染您的组件一次。
推荐阅读
- java - 使用 JPA 规范 (CriteriaQuery CriteriaBuilder) 按过滤后的 OneToMany 关系中的字段排序
- javascript - 没有重复特殊字符的正则表达式
- json - 如何从 openFoodFacts JSON(Swift) 解码
- python - 图像分类模型
- tensorflow - 如何使用自己的图像使用 MNIST 数据集和神经网络预测手写数字
- regex - 在 lua 5.1 中使用 string.gmatch 拆分字符串时包含空匹配项
- c - 不熟悉的c语言表达,与pthreads有关
- linux - 如何使用 libpqxx 以编程方式清除 PostgreSQL 数据库
- oracle - 如何建立参照完整性约束?
- java - 如何更新没有任何数据的行以唯一标识该行?