重新混合水合作用失败:服务器和客户端上的 UI 不匹配

nus*_*ara 11 javascript reactjs styled-components vercel remix.run

在本地没问题(已知警告并且 CSS 渲染良好),但在 Vercel 上我的 Remix 应用程序收到此错误:

Hydration 失败,因为初始 UI 与服务器上呈现的内容不匹配。

业务逻辑运行良好,但 CSS 完全损坏。

2022 年 6 月 26 日 15:50 更新

我从头开始一个新项目,并逐一添加依赖项,并在每一步中部署到 Vercel。没有错误。样式化的组件渲染良好。所以依赖关系不是问题。

然后,我开始通过加载器从数据库中逐段获取数据,并将它们一一呈现在样式组件中。始终破坏 CSS 并产生错误的一件事是在渲染之前将日期时间对象转换为字符串:

const DateTimeSpan = styled.span`
  font-size: 1rem;
`;

const hr = now.getHours();
const min = now.getMinutes();

<DateTimeSpan>
  {`${hr}:${min}`}
</DateTimeSpan>
Run Code Online (Sandbox Code Playgroud)

奇怪的是,只有当我将其格式化为仅渲染时间时,它才会中断。有了日期,就可以了:

const yr = now.getFullYear();
const mth = now.getMonth();
const dd = now.getDate();

<DateTimeSpan>
  {`${yr}-${mth}-${dd}`}
<DateTimeSpan>
Run Code Online (Sandbox Code Playgroud)

我无法解释这一点。

更新于 2022 年 7 月 2 日 21:55

使用上面相同的最简单的项目,我和朋友已经确定,当我们尝试渲染hours时,具有样式组件的 CSS 会中断,即:

const hr = now.getHours();

<DateTimeSpan>
  {hr}
</DateTimeSpan>
Run Code Online (Sandbox Code Playgroud)

我们怀疑样式组件会损坏,因为小时在服务器上以 UTC 时间呈现,但在客户端上以区域设置时间呈现。

我不确定这是否是一个错误,或者我们是否应该自己处理这个问题。也不确定是否应该在 Remix 或 Styled 组件 GitHub 问题上提出这个问题。无论如何,我已经在 Remix 上开了一个问题作为开始。

原帖

不确定,但可能与这些问题有关:

我通读了上面的内容和其他一些页面,我所能想到的就是更新一些依赖项。以下是可能相关的:

const hr = now.getHours();

<DateTimeSpan>
  {hr}
</DateTimeSpan>
Run Code Online (Sandbox Code Playgroud)

我的主要怀疑是它与样式组件有关,因为我之前在 Nextjs 上也遇到过类似的问题。但我的 app/root.tsx 和 app/entry.server.tsx 非常密切地遵循此示例的样式组件:

{
"react": "^18.2.0",
"styled-components": "^5.3.5"
"@remix-run/node": "^1.6.1",
"@remix-run/react": "^1.6.1",
"@remix-run/vercel": "^1.6.1",
"@vercel/node": "^2.2.0",
}
Run Code Online (Sandbox Code Playgroud)
// app/root.tsx

export default function App() {
  const data = useLoaderData();

  return (
    <Html lang="en">
      <head>
        ...
        {typeof document === "undefined" ? "__STYLES__" : null}
      </head>
      <Body>
        ...
      </Body>
    </Html>
  );
}
Run Code Online (Sandbox Code Playgroud)

与该示例最大的区别似乎是hydrate,我没有使用 ,而是使用hydrateRootReact 18 中应有的方式。不确定它是否对问题有任何影响:

//app/entry.server.tsx

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  const sheet = new ServerStyleSheet();

  let markup = renderToString(
    sheet.collectStyles(
      <RemixServer context={remixContext} url={request.url} />
    )
  );
  const styles = sheet.getStyleTags();
  markup = markup.replace("__STYLES__", styles);
  responseHeaders.set("Content-Type", "text/html");

  return new Response("<!DOCTYPE html>" + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}
Run Code Online (Sandbox Code Playgroud)

CSS-in-JS 库的 Remix 文档说:“使用样式组件时,您可能会遇到水合警告。希望这个问题很快就能得到解决。” 这个问题还没有解决,所以也许这个问题还没有解决方案。

但如果示例存储库有效,那么也许我错过了一些东西?

nus*_*ara 3

是的,渲染时间尤其是问题,因为服务器时间采用 UTC 格式,而客户端时间则是区域设置时间(UTC + X 小时)。这会导致两者的 UI 不同。

检查这一点的一种快速方法是在运行应用程序并尝试其页面之前将当前 CLI 实例的时区设置为 UTC:

export TZ=UTC

npm run dev
Run Code Online (Sandbox Code Playgroud)

我们将看到 CSS 出现问题,如上面的问题所述。

有多种方法可以解决此问题,具体针对不同的用例。一是不发送日期时间对象。相反,将其作为字符串发送。例如:

const now: Date = new Date()

// Locale time as example only, we need to know client's locale time
const time: string = now.toLocaleTimeString([], {
               hour: "2-digit",
               minute: "2-digit",
             })

// Send time string to client.
Run Code Online (Sandbox Code Playgroud)

这假设我们已经知道客户端的时区,因此我们可以使用它来设置/格式化服务器上​​的时间。

更灵活的方法是仅在页面装入后设置时间。例如:

const [now, setNow] = useState<Date>();
const loaderData = useLoaderData<string>();

useEffect(() => {
  if (!loaderData) {
    return;
  }

  setNow(JSON.parse(loaderData));
}, [loaderData]);

return <>{now.toLocaleTimeString([], {
            hour: "2-digit",
            minute: "2-digit",
          })}</>
Run Code Online (Sandbox Code Playgroud)

使用这个解决方案,我们失去了 SSR 的一些好处。这有一定的影响。例如,我们需要特别注意 SEO(查看页面源代码,我们不会看到正确呈现的日期)。如果我们不这样做,机器人将无法正确索引应用程序。