Как реализовать SSR для медиазапросов пользовательского интерфейса материалов в Next Js? - PullRequest
5 голосов
/ 29 апреля 2020

Я не могу следовать документации реализации медиазапросов пользовательского интерфейса Material, потому что она указана для простого приложения React, и я использую Next Js. В частности, я не знаю, куда поместить следующий код, указанный в документации:

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>
  }
)

Я знаю, что могу использовать пользовательский интерфейс материала 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>
    )   
  }
}

1 Ответ

2 голосов
/ 02 мая 2020

Первое предостережение - в настоящее время я сам не имею опыта использования SSR, но у меня есть глубокое знание 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 (или getServerSideProps в Next. js 9.3 или новее). getInitialProps получает объект контекста , из которого можно получить объект запроса 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>
    );
  }
}
...