首页 > 解决方案 > 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 期望服务器和客户端之间呈现的内容是相同的。它可以修补文本内容的差异,但您应该将不匹配视为错误并修复它们。

非常感谢您的帮助!

标签: reactjsnext.jsserver-side-rendering

解决方案


客户收到后,整个页面总是从头开始重新执行,我是对的吗?

是和不是。初始 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和渲染您的组件一次。


推荐阅读