【发布时间】: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)。注意组件树需要渲染两次:
第一次触发任何数据获取承诺;
然后,一旦解决了这些承诺,就会完成最后一遍,用可用的数据渲染树。
在服务器的标记被发送到客户端之后,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)中的错误,或者我是否遗漏了某些内容。
如果您需要我方面的任何详细信息,请告诉我。任何见解都值得赞赏!
提前致谢! ????
【问题讨论】:
-
您可以在codesandbox.io 之类的地方展示它的演示吗?
标签: reactjs material-ui server-side-rendering