我无法遵循documentation来实现Material UI的媒体查询,因为它是为纯React应用程序指定的,并且我正在使用NextJs。具体来说,我不知道将文档指定的以下代码放在哪里:
import ReactDOMServer from 'react-dom/server';
import parser from 'ua-parser-js';
import mediaQuery from 'css-mediaquery';
import { ThemeProvider } from '@material-ui/core/styles';
function handleRender(req, res) {
const deviceType = parser(req.headers['user-agent']).device.type || 'desktop';
const ssrMatchMedia = query => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: deviceType === 'mobile' ? '0px' : '1024px',
}),
});
const html = ReactDOMServer.renderToString(
<ThemeProvider
theme={{
props: {
// Change the default options of useMediaQuery
MuiUseMediaQuery: { ssrMatchMedia },
},
}}
>
<App />
</ThemeProvider>,
);
// …
}
之所以要实现这一点,是因为我使用媒体查询来有条件地呈现某些组件,例如:
const xs = useMediaQuery(theme.breakpoints.down('sm'))
...
return(
{xs ?
<p>Small device</p>
:
<p>Regular size device</p>
}
)
我知道我可以使用Material UI的Hidden
,但是我喜欢这种方法,其中媒体查询是具有状态的变量,因为我也使用它们有条件地应用CSS。
我已经在styled components
和Material UI的样式中使用了SRR。这是我的_app.js
import NextApp from 'next/app'
import React from 'react'
import { ThemeProvider } from 'styled-components'
const theme = {
primary: '#4285F4'
}
export default class App extends NextApp {
componentDidMount() {
const jssStyles = document.querySelector('#jss-server-side')
if (jssStyles && jssStyles.parentNode)
jssStyles.parentNode.removeChild(jssStyles)
}
render() {
const { Component, pageProps } = this.props
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
<style jsx global>
{`
body {
margin: 0;
}
.tui-toolbar-icons {
background: url(${require('~/public/tui-editor-icons.png')});
background-size: 218px 188px;
display: inline-block;
}
`}
</style>
</ThemeProvider>
)
}
}
这是我的_document.js
import React from 'react'
import { Html, Head, Main, NextScript } from 'next/document'
import NextDocument from 'next/document'
import { ServerStyleSheet as StyledComponentSheets } from 'styled-components'
import { ServerStyleSheets as MaterialUiServerStyleSheets } from '@material-ui/styles'
export default class Document extends NextDocument {
static async getInitialProps(ctx) {
const styledComponentSheet = new StyledComponentSheets()
const materialUiSheets = new MaterialUiServerStyleSheets()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props =>
styledComponentSheet.collectStyles(
materialUiSheets.collect(<App {...props} />)
)
})
const initialProps = await NextDocument.getInitialProps(ctx)
return {
...initialProps,
styles: [
<React.Fragment key="styles">
{initialProps.styles}
{materialUiSheets.getStyleElement()}
{styledComponentSheet.getStyleElement()}
</React.Fragment>
]
}
} finally {
styledComponentSheet.seal()
}
}
render() {
return (
<Html lang="es">
<Head>
<link
href="https://fonts.googleapis.com/css?family=Comfortaa|Open+Sans&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
答案 0 :(得分:5)
首先需要注意的是-我目前没有任何使用SSR的经验,但是我有deep knowledge of Material-UI,我认为,使用包含在问题中的代码和Next.js文档,我可以为您提供帮助你通过这个工作。
您已经在_app.js
中展示了如何将theme
设置到样式组件ThemeProvider
中。您还需要为Material-UI ThemeProvider设置一个主题,并且需要根据设备类型在两个可能的主题之间进行选择。
首先定义您关心的两个主题。这两个主题将使用ssrMatchMedia
的不同实现方式-一种用于移动设备,一种用于台式机。
import mediaQuery from 'css-mediaquery';
import { createMuiTheme } from "@material-ui/core/styles";
const mobileSsrMatchMedia = query => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: "0px"
})
});
const desktopSsrMatchMedia = query => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: "1024px"
})
});
const mobileMuiTheme = createMuiTheme({
props: {
// Change the default options of useMediaQuery
MuiUseMediaQuery: { ssrMatchMedia: mobileSsrMatchMedia }
}
});
const desktopMuiTheme = createMuiTheme({
props: {
// Change the default options of useMediaQuery
MuiUseMediaQuery: { ssrMatchMedia: desktopSsrMatchMedia }
}
});
为了在两个主题之间进行选择,您需要利用请求中的用户代理。这是我的知识很浅的地方,因此这里的代码中可能存在一些小问题。我认为您需要使用getInitialProps
(或Next.js 9.3或更高版本中的getServerSideProps
)。 getInitialProps
收到context object,您可以从中获取HTTP请求对象(req
)。然后,您可以按照与Material-UI文档示例相同的方式使用req
来确定设备类型。
以下是我认为_app.js
的外观的近似值(未执行,因此可能存在较小的语法问题,并且由于我从未使用过Next.js而在getInitialProps
中有一些猜测):
import NextApp from "next/app";
import React from "react";
import { ThemeProvider } from "styled-components";
import { createMuiTheme, MuiThemeProvider } from "@material-ui/core/styles";
import mediaQuery from "css-mediaquery";
import parser from "ua-parser-js";
const theme = {
primary: "#4285F4"
};
const mobileSsrMatchMedia = query => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: "0px"
})
});
const desktopSsrMatchMedia = query => ({
matches: mediaQuery.match(query, {
// The estimated CSS width of the browser.
width: "1024px"
})
});
const mobileMuiTheme = createMuiTheme({
props: {
// Change the default options of useMediaQuery
MuiUseMediaQuery: { ssrMatchMedia: mobileSsrMatchMedia }
}
});
const desktopMuiTheme = createMuiTheme({
props: {
// Change the default options of useMediaQuery
MuiUseMediaQuery: { ssrMatchMedia: desktopSsrMatchMedia }
}
});
export default class App extends NextApp {
static async getInitialProps(ctx) {
// I'm guessing on this line based on your _document.js example
const initialProps = await NextApp.getInitialProps(ctx);
// OP's edit: The ctx that we really want is inside the function parameter "ctx"
const deviceType =
parser(ctx.ctx.req.headers["user-agent"]).device.type || "desktop";
// I'm guessing on the pageProps key here based on a couple examples
return { pageProps: { ...initialProps, deviceType } };
}
componentDidMount() {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles && jssStyles.parentNode)
jssStyles.parentNode.removeChild(jssStyles);
}
render() {
const { Component, pageProps } = this.props;
return (
<MuiThemeProvider
theme={
pageProps.deviceType === "mobile" ? mobileMuiTheme : desktopMuiTheme
}
>
<ThemeProvider theme={theme}>
<Component {...pageProps} />
<style jsx global>
{`
body {
margin: 0;
}
.tui-toolbar-icons {
background: url(${require("~/public/tui-editor-icons.png")});
background-size: 218px 188px;
display: inline-block;
}
`}
</style>
</ThemeProvider>
</MuiThemeProvider>
);
}
}