如何在NextJs中为Material UI的媒体查询实现SSR?

2024-03-09

我无法遵循文档 https://material-ui.com/components/use-media-query/#server-side-rendering实现 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 UIHidden但我喜欢这种方法,其中媒体查询是具有状态的变量,因为我还使用它们有条件地应用 CSS。

我已经在使用了styled components以及具有 SRR 的 Material UI 样式。这是我的_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>
    )   
  }
}

首先需要注意的是——我自己目前没有任何使用 SSR 的经验,但我有对 Material-UI 的深入了解 https://stackoverflow.com/tags/material-ui/topusers我认为通过您问题中包含的代码和 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 (or getServerSideProps在 Next.js 9.3 或更高版本中)。getInitialProps收到上下文对象 https://nextjs.org/docs/api-reference/data-fetching/getInitialProps#context-object您可以从中获取 HTTP 请求对象(req)。然后你可以使用req以与 Material-UI 文档示例中相同的方式确定设备类型。

以下是我的想法的近似值_app.js应该看起来像(没有执行,所以可能有轻微的语法问题,并且有一些猜测getInitialProps因为我从未使用过 Next.js):

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>
    );
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在NextJs中为Material UI的媒体查询实现SSR? 的相关文章

随机推荐