【问题标题】:Loading state + second SSR rendering pass causing a client-side rendering glitch加载状态 + 第二个 SSR 渲染通道导致客户端渲染故障
【发布时间】:2020-07-31 07:31:43
【问题描述】:

我将material-ui 与 SSR 一起使用。我已经根据material-ui 文档上的instructions 在我的应用程序上设置了 SSR 机制。它确实有效,但并非没有迄今为止很难调试的渲染问题。更多细节如下。

SSR + 加载状态(导致相关组件无法在其中一个 SSR 渲染通道中渲染,更多内容见下文)导致在第二个 SSR 渲染通道上渲染的特定组件的类名中的 ID 不一致,但不是第一个(因为它的渲染取决于有可用的数据)。

这会导致从服务器发送的标记为此组件具有不同的 CSS 类名称,从而导致水合发生时的视觉不一致,如下所示:

SSRed 组件:

水合成分:

DOM 中可用的实际类是:

.PrivateSwitchBase-input-393 {
  top: 0;
  left: 0;
  width: 100%;
  cursor: inherit;
  height: 100%;
  margin: 0;
  opacity: 0;
  padding: 0;
  z-index: 1;
  position: absolute;
}

但是由于 CSS 类名不匹配,一个不存在的类 PrivateSwitchBase-input-411 被应用到 CheckBox input,并且它并没有像应有的那样变得不可见,导致在水合时出现视觉故障客户端。

我从 React 收到以下警告:

警告:道具className 不匹配。服务器: “PrivateSwitchBase-input-411”客户端:“PrivateSwitchBase-input-393”。

我希望 className 匹配并且组件呈现在服务器和客户端中是相同的。

复制步骤

我有一个TodoItem 组件:

import React from 'react';
import { 
  FormControlLabel,
  Checkbox
} from '@material-ui/core';

const TodoItem = (props) => {
  return (
    <FormControlLabel style={props.style} control={<Checkbox/>} label={props.title} />
  )
}

export default TodoItem;

还有一个Todos 组件(简化版):

import React from 'react';
import  SortableTree, { getFlatDataFromTree } from '../lib/sortable-tree';
import { observer } from "mobx-react";
import { useQuery } from '../models/reactUtils';
import { Paper } from '@material-ui/core';

const Todos = observer((props) => {
  const {store, loading} = useQuery(store => store.fetchActiveTodoTree());

  return (
    <>
      <Paper style={{padding: '20px'}}>
        <SortableTree
          treeData={store.activeTodoTree.toJSON()}
          generateNodeProps={({node, path}) => ({
            title: (
              <TodoItem title={node.title} />
            ),
          })}
        />
      </Paper>
  )
});

我加载了呈现Todos 组件的应用程序。该组件使用mst-gql从后端API加载一些数据并传递给SortableTree组件;

当从服务器运行时,我使用来自mst-gql 的getDataFromTree 函数等待数据承诺被解析并最终将HTML 发送回客户端(我在这里省略了这段代码,不过如果需要可以分享一下,貌似是here,只是我的版本用的是mst-gql而不是Redux)。注意组件树需要渲染两次:

  1. 第一次触发任何数据获取承诺;

  2. 然后,一旦解决了这些承诺,就会完成最后一遍,用可用的数据渲染树。

在服务器的标记被发送到客户端之后,React.hydrate 就会发生。那时,由于不存在 CSS 类,相关组件会使用可见输入呈现。

我确信问题的发生是因为上面的 2 点。 第一次渲染 Todos 组件时,store.activeTodoTree 数据尚不可用,因此 @987654352 @ 组件不渲染任何东西,因此 SortableTree 应该内联使用的 TodoItem 组件作为其树节点(请参阅上面的屏幕截图)不会在第一次渲染(但其他所有内容)。我不确切知道className ID 后缀生成逻辑在MUI 中是如何工作的,但正因为如此,PrivateSwitchBase-input 类的后缀(用于 MUI 的 CheckBox 组件的内部复选框输入)之间的 ID 不匹配服务器和客户端,导致我在上面的屏幕截图中显示的视觉故障。

不过,有趣的是,我Foobar 节点的子节点即使在水合之后也会按预期呈现,如下所示:

您可以看到这些节点的复选框输入是隐藏的,这意味着 CSS 类已正确应用。我不知道为什么这只发生在根节点上。

我设法找到了一个肮脏的解决方法:如果我添加一个始终在所有 SSR 渲染通道中渲染的虚拟对象,如下所示:

import  SortableTree, { getFlatDataFromTree } from '../lib/sortable-tree';
import { observer } from "mobx-react";
import { useQuery } from '../models/reactUtils';
import { Paper } from '@material-ui/core';

const Todos = observer((props) => {
  const {store, loading} = useQuery(store => store.fetchActiveTodoTree());

  return (
    <>
      <TodoItem title="I am here so that my className ID matches :("/> 
      <Paper style={{padding: '20px'}}>
        <SortableTree
          treeData={store.activeTodoTree.toJSON()}
          generateNodeProps={({node, path}) => ({
            title: (
              <TodoItem title={node.title} />
            ),
          })}
        />
      </Paper>
  )
});

然后问题就消失了,一切都从服务器和客户端中完美呈现。这证实了不匹配发生的理论,因为在第一个 SSR 渲染过程中,组件没有被渲染(作为 SortableTree 的一部分)。

环境

"@material-ui/core": "^4.9.10",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.49",
"mobx-react": "^6.1.8",
"mobx-state-tree": "^3.15.0",
"mst-gql": "^0.7.1"
"react": "^16.10.2",
"react-dnd": "7.3.0",
"react-dnd-html5-backend": "7.0.1",
"react-dom": "^16.10.2",
"react-helmet": "^5.2.1",
"react-helmet-async": "^1.0.2",

浏览器: Chrome 和 Firefox,最新版本。


我将如何处理这个问题?我无法确定这是否是我正在使用的某个库(MUI、mst-gql 和 SortableTree)中的错误,或者我是否遗漏了某些内容。

如果您需要我方面的任何详细信息,请告诉我。任何见解都值得赞赏!

提前致谢! ????

【问题讨论】:

标签: reactjs material-ui server-side-rendering


【解决方案1】:

我花了一些时间尝试按照@Girish 的建议提取一个最小的示例,并最终找到了问题所在。

它与material-ui 和mst-gql 无关。它与在react-router 的&lt;Switch&gt; 之外呈现的组件有关。

我有一个&lt;FlashMessage&gt; 组件,它基本上是material-ui 的&lt;SnackBar&gt; 的包装器。它曾经位于我的主要 App 组件的底部。它的显示由我观察到的一些 MST 属性控制。这是我的 App 组件的 JSX 标记:

<>
 <CssBaseline />
   <Helmet
     defaultTitle="Foobar"
   />
   <Switch>
     {this.flatRoutes}
   </Switch>
   <FlashMessage />
</>

使用上面的 JSX,我原来的帖子中报告的问题仍然存在。但是,如果我将其更改为:

<>
 <CssBaseline />
   <Helmet
     defaultTitle="Foobar"
   />
   <Switch>
     {this.flatRoutes}
      <FlashMessage />
   </Switch>
</>

然后问题不再发生。请注意,我将 &lt;FlashMessage/&gt; 组件移到了 'react-router 的 &lt;Switch&gt; 组件中。

我仍然不知道导致问题的原因的详细信息。如果我发现我会更新这篇文章。如果其他人有任何见解,请分享:)

【讨论】:

  • 与您的问题类似!谢谢您的回答。问题又是 React Router 中的一些不为人知的隐藏错误...
猜你喜欢
  • 2015-03-28
  • 2020-11-06
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 1970-01-01
  • 2018-06-05
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多