由于 className 的分配不同,我无法理解 Material-UI 组件中客户端和服务器端样式渲染之间的差异。
首次加载页面时,classNames 被正确分配,但刷新页面后,classNames 不再匹配,因此组件丢失了其样式。这是我在控制台上收到的错误消息:
警告:属性className不匹配。服务器:“MuiFormControl-root-3 MuiFormControl-marginNormal-4 SearchBar-textField-31 ”客户端:“MuiFormControl-root-3 MuiFormControl-marginNormal-4 SearchBar-textField-2 ”
className
我已经遵循了 Material-UI TextField示例文档及其附带的代码沙盒示例,但我似乎无法弄清楚是什么导致了服务器和客户端 classNames 之间的差异。
我在添加带有删除“x”图标的 Material-UI Chips 时遇到了类似的问题。刷新后,“x”图标呈现的宽度高达 1024px。相同的潜在问题是该图标没有收到正确的样式类。
Stack Overflow 上有几个问题,涉及为什么客户端和服务器可能以不同的方式呈现 classNames(例如,需要升级到 @Material-UI/core 版本 ^1.0.0、使用自定义 server.js 以及在 setState 中使用 Math.random),但这些都不适用于我的情况。
我不太清楚这个 Github 讨论是否有帮助,但可能没有帮助,因为他们使用的是 Material-UI 的测试版。
创建项目文件夹并启动 Node 服务器:
mkdir app cd app npm init -y npm install react react-dom next @material-ui/core npm run dev
添加到‘脚本’:"dev": "next",
"dev": "next",
import Head from "next/head" import CssBaseline from "@material-ui/core/CssBaseline" import SearchBar from "../components/SearchBar" const Index = () => ( <React.Fragment> <Head> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta charSet="utf-8" /> </Head> <CssBaseline /> <SearchBar /> </React.Fragment> ) export default Index
import PropTypes from "prop-types" import { withStyles } from "@material-ui/core/styles" import TextField from "@material-ui/core/TextField" const styles = (theme) => ({ container: { display: "flex", flexWrap: "wrap", }, textField: { margin: theme.spacing.unit / 2, width: 200, border: "2px solid red", }, }) class SearchBar extends React.Component { constructor(props) { super(props) this.state = { value: "" } this.handleChange = this.handleChange.bind(this) this.handleSubmit = this.handleSubmit.bind(this) } handleChange(event) { this.setState({ value: event.target.value }) } handleSubmit(event) { event.preventDefault() } render() { const { classes } = this.props return ( <form className={classes.container} noValidate autoComplete="off" onSubmit={this.handleSubmit} > <TextField id="search" label="Search" type="search" placeholder="Search..." className={classes.textField} value={this.state.value} onChange={this.handleChange} margin="normal" /> </form> ) } } SearchBar.propTypes = { classes: PropTypes.object.isRequired, } export default withStyles(styles)(SearchBar)
在浏览器中访问页面localhost:3000并看到以下内容:
localhost:3000
TextField 组件周围的红色边框
刷新浏览器,看到如下内容:
TextField 组件的样式消失了
请注意,TextField 周围的红色边框消失了。
问题在于 Next.js 中的 SSR 渲染,它在页面渲染之前产生了样式片段。
使用 Material UI 和 Next.js(作者正在使用),添加一个名为的文件_document.js解决了该问题。
_document.js
已调整_document.js(如此处建议):
import React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { ServerStyleSheets } from '@material-ui/styles'; // works with @material-ui/core/styles, if you prefer to use it. import theme from '../src/theme'; // Adjust here as well export default class MyDocument extends Document { render() { return ( <Html lang="en"> <Head> {/* Not exactly required, but this is the PWA primary color */} <meta name="theme-color" content={theme.palette.primary.main} /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); } } // `getInitialProps` belongs to `_document` (instead of `_app`), // it's compatible with server-side generation (SSG). MyDocument.getInitialProps = async (ctx) => { // Resolution order // // On the server: // 1. app.getInitialProps // 2. page.getInitialProps // 3. document.getInitialProps // 4. app.render // 5. page.render // 6. document.render // // On the server with error: // 1. document.getInitialProps // 2. app.render // 3. page.render // 4. document.render // // On the client // 1. app.getInitialProps // 2. page.getInitialProps // 3. app.render // 4. page.render // Render app and page and get the context of the page with collected side effects. const sheets = new ServerStyleSheets(); const originalRenderPage = ctx.renderPage; ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheets.collect(<App {...props} />), }); const initialProps = await Document.getInitialProps(ctx); return { ...initialProps, // Styles fragment is rendered after the app and page rendering finish. styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], }; };