我正在将material-ui
与SSR一起使用。我已经根据material-ui
文档上的instructions在我的应用上设置了SSR机制。它确实可以工作,但是并非没有渲染问题,到目前为止,这一直很难调试。详细信息如下。
SSR +加载状态(导致所涉及的组件无法在其中一个SSR渲染过程中渲染,有关更多信息,请参见下文)导致在第二个SSR渲染过程中渲染的特定组件的className中的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
数据尚未出现可用,因此SortableTree
组件不会呈现任何内容,因此应该由TodoItem
内联使用的SortableTree
作为其树节点(请参见上面的屏幕截图)不是第一次渲染(但其他一切都是)。我不知道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
)中的错误,或者是否丢失了某些东西。
如果您需要我的任何帮助,请告诉我。任何见解表示赞赏!
提前谢谢! ?
答案 0 :(得分:1)
我花了一些时间尝试提取@Girish建议的最小示例,最终发现了问题。
它与material-ui
或mst-gql
也无关。它与在react-router
的{{1}}之外呈现的组件有关。
我有一个<Switch>
组件,基本上是<FlashMessage>
的{{1}}的包装。它曾经位于我的主要App组件的底部。它的显示是由一些观察到的MST属性控制的。这是我的App组件的JSX标记:
material-ui
使用上述JSX,我的原始帖子中报告的问题仍然存在。但是,如果我将其更改为:
<SnackBar>
然后该问题不再发生。注意,我将<>
<CssBaseline />
<Helmet
defaultTitle="Foobar"
/>
<Switch>
{this.flatRoutes}
</Switch>
<FlashMessage />
</>
组件移到了“反应路由器”的<>
<CssBaseline />
<Helmet
defaultTitle="Foobar"
/>
<Switch>
{this.flatRoutes}
<FlashMessage />
</Switch>
</>
组件内。
我仍然不知道为什么这导致了问题的详细信息。如果我发现了,我将更新这篇文章。如果其他人有任何见解,请分享:)