我对使用Material UI以及更具体地说是系统功能(@ material-ui / system)进行服务器端渲染有疑问。
在我的ReactJS应用程序中,我有以下代码段(在此示例中,我使用Material-ui的Flexbox系统道具):
<Box display="flex">Hello</Box>
当服务器向我发送HTML页面时,源代码中显示了“ display”属性,但是当客户端进行水合作用时,我在DOM上找不到此组件的属性“ display:flex”
但是,当我使用'style'属性时,我会在DOM中找到此属性。
<Box style={{display:'flex'}}>Hello</Box>
我想弄清楚,当我以这种方式导入Box组件时,它不起作用:
import Box from '@material-ui/core/Box';
另一方面,当我通过Styled-Component以这种方式导入它时,它会起作用:
import styled from 'styled-components';
const Box = styled.div`${display}`;
有关更多信息,这里是我的服务器和客户端的配置。
server.js
import express from 'express';
import fs from 'fs';
import Render from './Render';
import { resolveAppPath } from '../utils/resolve-app-path';
import { conf } from '../../i18n';
const middleware = require('i18next-express-middleware');
const i18next = require('i18next');
const dev = process.env.NODE_ENV === 'development';
import devMiddleware from './dev-middleware';
const app = express();
const port = process.env.PORT || 80;
if (dev) {
devMiddleware(app);
}
app.use('/assets',express.static(resolveAppPath('dist/assets')));
i18next.use(middleware.LanguageDetector).init(conf);
app.use(middleware.handle(i18next));
app.get('*',(req,res) => {
const index = resolveAppPath('dist/assets/index.html');
fs.readFile(index,'utf8',(err,data) => {
if (err) {
console.error('error when reading index file : ',err);
res.status(500).send('error when reading index file');
}
res.status(200).send(Render(req,data));
});
});
app.listen(port,() => {
console.log('Server started on port : ' + port);
});
Render.js
import ReactDOMServer from 'react-dom/server';
import App from '../shared/components/App';
import React from 'react';
import { ServerStyleSheets } from '@material-ui/core/styles';
import { ServerStyleSheet } from 'styled-components';
import { I18nextProvider } from 'react-i18next';
import { StaticRouter } from 'react-router-dom';
const Render = (req,data) => {
const context = {};
const muiSheet = new ServerStyleSheets();
const scSheet = new ServerStyleSheet();
try {
const html = ReactDOMServer.renderToString(
muiSheet.collect(
scSheet.collectStyles(
<StaticRouter location={req.url} context={context}>
<I18nextProvider i18n={req.i18n}>
<App />
</I18nextProvider>
</StaticRouter>,),);
const muiCss = muiSheet.toString();
const scCss = scSheet.getStyletags();
data = data.replace('<div id="root"></div>',`<div id="root">${html}</div>`);
data = data.replace('<style id="jss-server-side"></style>',`<style id="jss-server-side">${muiCss}</style>`);
data = data.replace('</head>',`${scCss}</head>`);
} catch (error) {
console.error(error);
} finally {
scSheet.seal();
}
return data;
};
client.js
import React from 'react';
import { hydrate,render } from 'react-dom';
import App from 'Components/App';
import { I18nextProvider } from 'react-i18next';
import i18n from '../../i18n';
import { BrowserRouter } from 'react-router-dom';
import { AppContainer } from 'react-hot-loader';
function Main() {
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentNode.removeChild(jssStyles);
}
},[]);
return <App />;
}
const renderApp = Component => {
hydrate(
<AppContainer>
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<Component />
</I18nextProvider>
</BrowserRouter>
</AppContainer>,document.getElementById('root'),);
};
renderApp(Main);
if (module.hot) {
module.hot.accept('Components/App',() => {
renderApp(Main);
});
}
App.js
import React from 'react';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider } from 'styled-components';
import { ThemeProvider as ThemeProviderMUI,StylesProvider } from '@material-ui/core/styles';
import theme from '../theme';
import Box from '@material-ui/core/Box';
const App = () => {
return (
<div>
<CssBaseline />
<StylesProvider injectFirst>
<ThemeProviderMUI theme={theme}>
<ThemeProvider theme={theme}>
<Box display="flex"></Box>
</ThemeProvider>
</ThemeProviderMUI>
</StylesProvider>
</div>
);
};
export default App;
index.html
<!DOCTYPE html>
<html lang="fr">
<head>
<base href="/" />
<title>Mario Laporte - Développeur web</title>
<meta name="description" content="Mario Laporte - Développeur web" />
<meta charset="UTF-8" />
<meta name="viewport" content="minimum-scale=1,initial-scale=1,width=device-width,shrink-to-fit=no" />
<meta name="mobile-web-app-capable" content="yes" />
<!--
# List of devices and resolutions (AUG-2019):
#
# Device Portrait size Landscape size Screen size Pixel ratio
# iPhone SE 640px × 1136px 1136px × 640px 320px × 568px 2
# iPhone 8 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone 7 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone 6s 750px × 1334px 1334px × 750px 375px × 667px 2
# iPhone XR 828px × 1792px 1792px × 828px 414px × 896px 2
# iPhone XS 1125px × 2436px 2436px × 1125px 375px × 812px 3
# iPhone X 1125px × 2436px 2436px × 1125px 375px × 812px 3
# iPhone 8 Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone 7 Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone 6s Plus 1242px × 2208px 2208px × 1242px 414px × 736px 3
# iPhone XS Max 1242px × 2688px 2688px × 1242px 414px × 896px 3
# 9.7" iPad 1536px × 2048px 2048px × 1536px 768px × 1024px 2
# 7.9" iPad mini 4 1536px × 2048px 2048px × 1536px 768px × 1024px 2
# 10.5" iPad Pro 1668px × 2224px 2224px × 1668px 834px × 1112px 2
# 11" iPad Pro 1668px × 2388px 2388px × 1668px 834px × 1194px 2
# 12.9" iPad Pro 2048px × 2732px 2732px × 2048px 1024px × 1366px 2
-->
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0640x1136_icon.png" media="(min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0750x1334_icon.png" media="(min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/0828x1792_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1125x2436_icon.png" media="(min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1242x2208_icon.png" media="(min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1242x2688_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1536x2048_icon.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1668x2224_icon.png" media="(min-device-width: 834px) and (max-device-width: 1112px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/1668x2388_icon.png" media="(min-device-width: 834px) and (max-device-width: 1194px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/portrait/2048x2732_icon.png" media="(min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1136x0640_icon.png" media="(min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1334x0750_icon.png" media="(min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/1792x0828_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2436x1125_icon.png" media="(min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2208x1242_icon.png" media="(min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2688x1242_icon.png" media="(min-device-width: 414px) and (max-device-width: 896px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2048x1536_icon.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2224x1668_icon.png" media="(min-device-width: 834px) and (max-device-width: 1112px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2388x1668_icon.png" media="(min-device-width: 834px) and (max-device-width: 1194px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="apple-touch-startup-image" href="icons/ios/startup/landscape/2732x2048_icon.png" media="(min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape)" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<style id="jss-server-side"></style>
</head>
<body>
<div id="root"></div>
<noscript>
Vous devez activer JavaScript pour exécuter cette application.
</noscript>
</body>
</html>
您知道这种行为是否正常吗? Material UI的系统功能与服务器端渲染不兼容?
我不认为问题出在我的配置上,因为如果不使用系统功能,我也没问题。
此外,渲染是在开发环境中使用dom属性完成的,而不是在生产环境中完成的。真奇怪 在这个问题上我找不到任何答案,您可能有个主意吗?
我还最小化了Webpack的配置,这是我使用的方法:
webpack.common.config.js
const ImageminPlugin = require('imagemin-webpack-plugin').default;
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const { resolveAppPath } = require('../../utils/resolve-app-path');
module.exports = {
entry: ['babel-polyfill','./src/client'],output: { path: resolveAppPath('dist/assets'),filename: '[name].js',publicPath: '/assets' },resolve: {
extensions: ['.ts','.tsx','.js','.jsx','.json'],alias: {
Components: resolveAppPath('src/shared/components'),Src: resolveAppPath('src'),Hooks: resolveAppPath('src/hooks'),Node: resolveAppPath('node_modules'),Img: resolveAppPath('public/images'),'react-dom': '@hot-loader/react-dom',},module: {
rules: [
{ test: /\.(ts|js)x?$/,exclude: /node_modules/,loader: ['babel-loader','eslint-loader'] },{ test: /\.css$/,use: ['style-loader','css-loader'] },{ test: /\.scss$/,'css-loader','sass-loader'] },{ test: /\.(png|jpg|gif)$/,use: ['file-loader'] },],plugins: [
new HtmlWebPackPlugin({
template: resolveAppPath('public/index.html'),favicon: resolveAppPath('public/favicon.ico'),}),new CopyWebpackPlugin([
{
from: resolveAppPath('public/images/icons/ios/startup'),to: resolveAppPath('dist/assets/icons/ios/startup'),]),new ImageminPlugin({
pngquant: {
speed: 11,quality: 100,cacheFolder: resolveAppPath('cache'),};
webpack.dev.config.js
const webpack = require('webpack');
const { resolveAppPath } = require('../../utils/resolve-app-path');
module.exports = {
entry: ['react-hot-loader/patch','webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000'],mode: 'development',target: 'web',devtool: 'eval-source-map',plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),new webpack.HotModuleReplacementPlugin(),new webpack.NoEmitOnErrorsPlugin(),};
webpack.prod.config.js
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',plugins: [
new CleanWebpackPlugin(),new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),};
非常感谢您抽出宝贵的时间回答我。