跳转到内容

useMediaQuery

这是 React 的 CSS 媒体查询 (Media queries)hook。 它监听与 CSS 媒体查询的匹配的内容。 它允许根据查询的结果是否匹配来渲染组件。

以下是一些重要的特点:

  • ⚛️ 它有一个符合用户使用习惯的 React API。
  • 🚀 它是高性能的,原理是通过观测文档的媒体查询值发生更改,而不是使用定期轮询的方法来监听其结果。
  • 📦 1kB 已压缩的包
  • 🤖 它支持服务器端渲染。

简单的媒体查询

你应该将媒体查询提供给 hook 作为第一个参数。 媒体查询的字符串可以是任何有效的 CSS 媒体查询,例如 '(prefers-color-scheme: dark)'

(min-width:600px) matches: false
import * as React from 'react';
import useMediaQuery from '@mui/material/useMediaQuery';

export default function SimpleMediaQuery() {
  const matches = useMediaQuery('(min-width:600px)');

  return <span>{`(min-width:600px) matches: ${matches}`}</span>;
}

⚠️ 由于每个浏览器的限制,你不能使用 'print',例如 Firefox 上的这个问题。

使用 Material UI 的断点辅助功能

按照如下所示的例子,你可以这样使用 Material UI 的 断点辅助功能

import { useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

function MyComponent() {
  const theme = useTheme();
  const matches = useMediaQuery(theme.breakpoints.up('sm'));

  return <span>{`theme.breakpoints.up('sm') matches: ${matches}`}</span>;
}
theme.breakpoints.up('sm') matches: false

或者你也可以使用一个回调函数,其第一个参数是 theme:

import useMediaQuery from '@material-ui/core/useMediaQuery';

function MyComponent() {
  const matches = useMediaQuery((theme) => theme.breakpoints.up('sm'));

  return <span>{`theme.breakpoints.up('sm') matches: ${matches}`}</span>;
}

⚠️ 由于这个方法没有默认的主题支持,所以你必须将它注入到父级主题提供者(parent theme provider)中。

使用 JavaScript 的语法

你可以使用 json2mq 来从 JavaScript 对象中生成媒体查询字符串。

{ minWidth: 600 } matches: false
import * as React from 'react';
import json2mq from 'json2mq';
import useMediaQuery from '@mui/material/useMediaQuery';

export default function JavaScriptMedia() {
  const matches = useMediaQuery(
    json2mq({
      minWidth: 600,
    }),
  );

  return <span>{`{ minWidth: 600 } matches: ${matches}`}</span>;
}

测试

你需要在测试环境中实现 matchMedia

例如:暂时还不支持 jsdom。 所以你应来兼容(polyfill)它。 我们推荐使用 css-mediaquery 来创造一个模拟环境从而达到兼容的目的。

import mediaQuery from 'css-mediaquery';

function createMatchMedia(width) {
  return (query) => ({
    matches: mediaQuery.match(query, {
      width,
    }),
    addListener: () => {},
    removeListener: () => {},
  });
}

describe('MyTests', () => {
  beforeAll(() => {
    window.matchMedia = createMatchMedia(window.innerWidth);
  });
});

仅在客户端渲染

要和服务器进行同步使用(hydration),hook 需要渲染两次。 第一次使用 false 表示服务端的值,第二次使用已解析的值。 这个双向渲染周期带有一个缺点。 速度较慢。 如果你只需要客户端渲染,那么你可以设置 noSsr 选项为 true

const matches = useMediaQuery('(min-width:600px)', { noSsr: true });

或者你可以通过全局主题设置来启用它:

const theme = createTheme({
  components: {
    MuiUseMediaQuery: {
      defaultProps: {
        noSsr: true,
      },
    },
  },
});

服务端渲染

⚠️ 从根本上来看,服务端渲染和客户端的媒体查询是矛盾的。 所以你需要在其中取舍。 支持只能是部分的。

你可以先尝试依赖于客户端的 CSS 媒体查询。 例如,你可以使用:

如果上述的方案都不可用,那么你也可以继续阅读本节文档的其余内容。

首先,你需要从服务端上猜测客户端请求的特征。 你可以选择使用:

  • 用户代理(User agent)。 解析客户端上用户代理的字符串来提取信息。 我们推荐使用 ua-parser-js 来解析用户代理信息。
  • 客户端提示(Client hints)。 读取客户端向服务器发送的提示。 请注意,并不是所有浏览器都会支持 此功能。

最后,你需要为 useMediaQuery 提供一个具有预先猜测特征的 matchMedia 来实现。 我们建议使用 css-mediaquery 来模拟 matchMedia 环境。

例如,在服务端上:

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, {
      // 浏览器的 CSS 宽度预计值
      width: deviceType === 'mobile' ? '0px' : '1024px',
    }),
  });

  const html = ReactDOMServer.renderToString(
    <ThemeProvider
      theme={{
        props: {
          // 更改 useMediaQuery 的默认选项
          MuiUseMediaQuery: {
            ssrMatchMedia,
          },
        },
      }}
    >
      <App />
    </ThemeProvider>,
  );

  // …
}
      width: deviceType === 'mobile' ? '0px' : '1024px',
    }),
  });

  const html = ReactDOMServer.renderToString(
    <ThemeProvider
      theme={{
        props: {
          // 更改 useMediaQuery 的默认选项
          MuiUseMediaQuery: {
            ssrMatchMedia,
          },
        },
      }}
    >
      <App />
    </ThemeProvider>,
  );

  // …
}
      width: deviceType === 'mobile' ? '0px' : '1024px',
    }),
  });

  const theme = createTheme({
    components: {
      // Change the default options of useMediaQuery
      MuiUseMediaQuery: {
        defaultProps: {
          ssrMatchMedia,
        },
      },
    },
  });

  const html = ReactDOMServer.renderToString(
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>,
  );

  // …
}
      width: deviceType === 'mobile' ? '0px' : '1024px',
    }),
  });

  const theme = createTheme({
    components: {
      // Change the default options of useMediaQuery
      MuiUseMediaQuery: {
        defaultProps: {
          ssrMatchMedia,
        },
      },
    },
  });

  const html = ReactDOMServer.renderToString(
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>,
  );

  // …
}
(min-width:600px) matches: true

确保您提供相同的自定义匹配媒体实现到客户端,这样能够保证注水渲染的匹配。

withWidth() 迁移

withWidth() 高阶组件注入了页面的屏幕宽度。 您可以使用 useWidth hook 来实现相同的操作:

width: xs
<ThemeProvider theme={theme}>
  <MyComponent />
</ThemeProvider>

API

useMediaQuery(query, [options]) => matches

参数

  1. query (string | func): A string representing the media query to handle or a callback function accepting the theme (in the context) that returns a string.
  2. options (object [optional]):
  • options.defaultMatches (bool [optional]): As window.matchMedia() is unavailable on the server, we return a default matches during the first mount. 默认值为 false。 默认值为 false
  • options.matchMedia (func [optional]): You can provide your own implementation of matchMedia. 用其您可以处理一个 iframe 内容窗口。 用其您可以处理一个 iframe 内容窗口。
  • options.noSsr (bool [optional]):默认为 false。 要和服务器进行同步使用(hydration),hook 需要渲染两次。 A first time with false, the value of the server, and a second time with the resolved value. 这个双向渲染周期带有一个缺点。 这个双向渲染周期带有一个缺点。 速度较慢。 如果你只需要 客户端渲染,那么可以将该选项设置为 true
  • options.ssrMatchMedia (func [optional]): You can provide your own implementation of matchMedia in a server-side rendering context.

注意:你可以使用主题的 默认属性 功能和 MuiUseMediaQuery 键(key)来更改默认的选项。

返回结果

matches:如果文档当前能够匹配这个媒体查询,Matches 则为 true ,否则为 false

例子

import * as React from 'react';
import useMediaQuery from '@material-ui/core/useMediaQuery';

export default function SimpleMediaQuery() {
  const matches = useMediaQuery('(min-width:600px)');

  return <span>{`(min-width:600px) matches: ${matches}`}</span>;
}