简体   繁体   English

加载 state + 第二个 SSR 渲染通道导致客户端渲染故障

[英]Loading state + second SSR rendering pass causing a client-side rendering glitch

I'm using material-ui with SSR.我将material-ui与 SSR 一起使用。 I've set up the SSR machinery on my app according to the instructions on the material-ui docs.我已经根据material-ui文档上的说明在我的应用程序上设置了 SSR 机制。 It does work, but not without a rendering issue that so far has been very hard to debug.它确实有效,但并非没有迄今为止很难调试的渲染问题。 More details follow.更多细节如下。

SSR + loading state (which causes the comp. in question to not render in one of the SSR rendering passes, more on that below) cause inconsistent ID in the className of a specific component that renders on the second SSR rendering pass but not on the first (because its rendering is conditioned to having the data available). SSR + 加载 state(这会导致相关组件无法在其中一个 SSR 渲染通道中渲染,更多内容见下文)导致在第二个 SSR 渲染通道上渲染但不在首先(因为它的渲染取决于有可用的数据)。

This causes the markup sent from the server to have a different CSS class name for this component, causing a visual inconsistency in when hydration happens, as you may see below:这会导致从服务器发送的标记具有不同的 CSS class 该组件的名称,从而导致水合发生时的视觉不一致,如下所示:

SSRed component: SSRed 组件:

在此处输入图像描述

Hydrated component:水合成分:

水合成分

The actual class available in the DOM is: DOM 中可用的实际 class 是:

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

But because of the CSS class name mismatch, an inexistent class PrivateSwitchBase-input-411 is applied to the CheckBox input , and it's not made invisible, as it should be, resulting in the visual glitch upon hydration in the client-side. But because of the CSS class name mismatch, an inexistent class PrivateSwitchBase-input-411 is applied to the CheckBox input , and it's not made invisible, as it should be, resulting in the visual glitch upon hydration in the client-side.

And I get the following warning from React:我从 React 收到以下警告:

Warning: Prop className did not match.警告: className不匹配。 Server: "PrivateSwitchBase-input-411" Client: "PrivateSwitchBase-input-393".服务器:“PrivateSwitchBase-input-411”客户端:“PrivateSwitchBase-input-393”。

I'd expect the className to match and the component rendering to be the same in both the server and the client.我希望className匹配并且组件渲染在服务器和客户端中都是相同的。

Steps to Reproduce重现步骤

I have a TodoItem component:我有一个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;

And a Todos component (simplified version):还有一个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>
  )
});

I load the app that renders the Todos component.我加载了呈现Todos组件的应用程序。 This component loads some data from the backend API using mst-gql and passes over to the SortableTree component;该组件使用mst-gql从后端 API 加载一些数据并传递给SortableTree组件;

When running from the server, I use thegetDataFromTree function from mst-gql to wait for the data promises to be resolved and finally get the HTML to be sent back to the client (I've omitted this code from here, but can share it if needed. It looks like the one here , just that my version uses mst-gql instead of Redux ).从服务器运行时,我使用来自mst-gqlgetDataFromTree function 等待数据承诺得到解决,最后将 HTML 发送回客户端(我在这里省略了此代码,但可以分享它如果需要。看起来像这里的那个,只是我的版本使用mst-gql而不是Redux )。 Note that the component tree needs to be rendered twice :请注意,组件树需要渲染两次

  1. The first time to trigger any data fetching promises;第一时间触发任何数据获取承诺;

  2. Then once these promises are resolved, the last pass is done to render the tree with the data that became available.然后,一旦解决了这些承诺,就会完成最后一遍以使用可用的数据渲染树。

After the markup from the server is sent to the client, then React.hydrate takes place.在将来自服务器的标记发送到客户端之后,就会发生React.hydrate That's when the component in question is then rendered with the visible input because of the inexistent CSS class.那时,由于不存在 CSS class,因此使用可见输入呈现有问题的组件。

I'm convinced the problem happens because of point 2 above.我确信问题的发生是因为上面的第2点。 The first time the Todos component is rendered, the store.activeTodoTree data is not yet available, so the SortableTree component doesn't render anything, hence the TodoItem component that's supposed to be used inline by the SortableTree as its tree nodes (refer to the screenshots above) is not rendered the first time (but everything else is).第一次渲染Todos组件时, store.activeTodoTree数据尚不可用,因此SortableTree组件不渲染任何内容,因此应该由SortableTree内联使用的TodoItem组件作为其树节点(请参阅上面的屏幕截图)不是第一次渲染(但其他一切都是)。 I don't know exactly how the className ID suffix generation logic works in MUI , but because of this, the suffix for the PrivateSwitchBase-input class (used for MUI's CheckBox component's internal checkbox input) has a mismatch of IDs between the server and the client, causing the visual glitch I've shown in the screenshots above.我不确切知道className ID 后缀生成逻辑在MUI中是如何工作的,但正因为如此, PrivateSwitchBase-input class 的后缀(用于 MUI 的 CheckBox 组件的内部复选框输入)在服务器和服务器之间的 ID 不匹配客户端,导致我在上面的屏幕截图中显示的视觉故障。

One interesting thing though, i s that the child nodes of the Foobar node, all render as expected even after hydration, as you may see below:不过,一件有趣的事情是, Foobar节点的子节点即使在水合之后也都按预期呈现,如下所示:

发生水合时,子树节点不会受到 SSR 渲染故障的影响

You can see that the checkbox input for these nodes are hidden, which means the CSS class was correctly applied.您可以看到这些节点的复选框输入是隐藏的,这意味着 CSS class 已正确应用。 I have no idea why that only happens to the root node though.我不知道为什么这只发生在根节点上。

I managed to find a dirty workaround though: If I add a dummy that is always rendered in all SSR rendering passes, like this:不过,我设法找到了一个肮脏的解决方法:如果我添加一个始终在所有 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>
  )
});

Then the issue goes away and everything is rendered perfectly both from the server and upon hydrating in the client.然后问题就消失了,一切都从服务器和客户端中完美呈现。 This confirms the theory that the mismatch happens because in the first SSR rendering pass the component is not rendered (as part of the SortableTree).这证实了不匹配发生的理论,因为在第一个 SSR 渲染过程中,组件没有被渲染(作为 SortableTree 的一部分)。

Environment环境

"@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",

Browser: Chrome and Firefox, latest versions.浏览器: Chrome 和 Firefox,最新版本。


How would I deal with this?我将如何处理? I couldn't find out if it's a bug in one of the libraries I'm using ( MUI , mst-gql and SortableTree ) or if perhaps I missing something.我无法确定这是否是我正在使用的库之一( MUImst-gqlSortableTree )中的错误,或者我是否遗漏了一些东西。

Let me know if you need any details from my side.如果您需要我这边的任何详细信息,请告诉我。 Any insights appreciated!任何见解表示赞赏!

Thanks in advance!提前致谢!

I spent some time trying to extract a minimal example as suggested by @Girish and ended up finding the issue.我花了一些时间尝试提取@Girish 建议的最小示例,并最终找到了问题。

It isn't related to material-ui nor mst-gql .它与material-uimst-gql无关。 It was related to a component being rendered outside a react-router 's <Switch> .它与在react-router<Switch>之外呈现的组件有关。

I have a <FlashMessage> component that's basically a wrapper around material-ui 's <SnackBar> .我有一个<FlashMessage>组件,它基本上是material-ui<SnackBar>的包装器。 It used to sit at the bottom of my main App component.它曾经位于我的主要 App 组件的底部。 Its display is controlled my some observed MST properties.它的显示由我观察到的一些 MST 属性控制。 Here's the JSX markup for my App component:这是我的 App 组件的 JSX 标记:

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

With the JSX above, the issue reported in my original post still happens.使用上面的 JSX,我原来的帖子中报告的问题仍然存在。 However, If I change it to:但是,如果我将其更改为:

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

Then the issue doesn't happen anymore.然后问题不再发生。 Notice I moved the <FlashMessage/> component inside 'react-router's <Switch> component.请注意,我将<FlashMessage/>组件移到了 'react-router 的<Switch>组件中。

I still don't know the details of why this was causing the issue.我仍然不知道为什么这会导致问题的详细信息。 If I ever find out I'll update this post.如果我发现我会更新这篇文章。 If anyone else has any insights, please share:)如果其他人有任何见解,请分享:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 PageSpeed Insights:服务器端渲染还是客户端渲染? - PageSpeed Insights: Server-side rendering or Client-side rendering? ReactJS 服务器端渲染与客户端渲染 - ReactJS server-side rendering vs client-side rendering React:具有客户端渲染的多个页面 - React: Multiple pages with client-side rendering 没有客户端 javascript 的服务器端渲染 - serverside rendering with no client-side javascript 服务器端渲染和客户端渲染之间有什么区别? - What is the difference between server-side and client-side rendering? React SSR:防止客户端呈现在服务器上呈现的组件 - React SSR: Prevent client side rendering of components which are rendered on the server 授权验证相关组件的安全客户端条件呈现 - Secure client-side conditional rendering of auth validation dependent components NextJS静态渲染后执行客户端DOM操作 - Executing client-side DOM manipulation after static rendering in NextJS 哪种方法更快,更快:服务器端呈现与客户端呈现 - Which method is faster, express : Server-side rendering vs client-side rendering 在 react webapp 中同时使用服务器端渲染和客户端渲染 - using both server-side rendering and client-side rendering in a react webapp
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM