跳到内容
+

useMediaQuery

这个 React Hook 监听 CSS 媒体查询的匹配情况。它允许根据查询是否匹配来渲染组件。

一些主要特性

  • ⚛️ 它具有符合语言习惯的 React API。
  • 🚀 它性能卓越,它观察文档以检测其媒体查询何时更改,而不是定期轮询值。
  • 📦 1.1 kB gzipped
  • 🤖 它支持服务端渲染。

基本媒体查询

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

(min-width:600px) matches: false
Enter 键开始编辑

⚠️ 由于浏览器的限制,您不能使用 'print',例如 Firefox

使用断点助手

您可以如下使用 Material UI 的 断点助手

import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/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 '@mui/material/useMediaQuery';

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

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

⚠️ 没有默认主题支持,您必须将其注入到父主题提供器中。

使用 JavaScript 语法

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

{ minWidth: 600 } matches: false
Enter 键开始编辑

测试

您的测试环境中需要 matchMedia 的实现。

例如,jsdom 尚不支持它。您应该对其进行 polyfill。建议使用 css-mediaquery 来模拟它。

import mediaQuery from 'css-mediaquery';

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

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

仅客户端渲染

为了执行服务端 hydration,Hook 需要渲染两次。第一次使用 defaultMatches,即服务器的值,第二次使用已解析的值。这种双程渲染周期有一个缺点:它速度较慢。如果您在客户端使用返回值,则可以将 noSsr 选项设置为 true

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

或者可以通过主题全局启用它

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

服务端渲染

首先尝试依赖客户端 CSS 媒体查询。例如,您可以使用

如果以上替代方案都不可行,您可以继续阅读文档的此部分。

首先,您需要从服务器猜测客户端请求的特征。您可以在使用以下方式之间进行选择

  • 用户代理。解析客户端的用户代理字符串以提取信息。建议使用 ua-parser-js 来解析用户代理。
  • 客户端提示。读取客户端发送给服务器的提示。请注意,此功能并非在所有地方都受支持

最后,您需要为 useMediaQuery 提供 matchMedia 的实现,并带有先前猜测的特征。建议使用 css-mediaquery 来模拟 matchMedia。

例如,在服务端

import * as ReactDOMServer from 'react-dom/server';
import parser from 'ua-parser-js';
import mediaQuery from 'css-mediaquery';
import { createTheme, ThemeProvider } from '@mui/material/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 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

确保您为客户端提供相同的自定义 matchMedia 实现,以保证 hydration 匹配。

withWidth() 迁移

withWidth() 高阶组件注入页面的屏幕宽度。您可以使用 useWidth Hook 重现相同的行为

width: xs
Enter 键开始编辑

API

useMediaQuery(query, [options]) => matches

参数

  1. query (string | func): 表示要处理的媒体查询的字符串,或接受 theme(在上下文中)并返回字符串的回调函数。
  2. options (object [可选])
  • options.defaultMatches (bool [可选]): 由于 window.matchMedia() 在服务器上不可用,因此它在首次挂载期间返回默认匹配项。默认值为 false
  • options.matchMedia (func [可选]): 您可以提供自己的 matchMedia 实现。这可以用于处理 iframe 内容窗口。
  • options.noSsr (bool [可选]): 默认为 false。为了执行服务端 hydration,Hook 需要渲染两次。第一次使用 defaultMatches,即服务器的值,第二次使用已解析的值。这种双程渲染周期有一个缺点:它速度较慢。如果您在客户端使用返回值,则可以将此选项设置为 true
  • options.ssrMatchMedia (func [可选]): 您可以提供自己的 matchMedia 实现,它在服务端渲染时使用。

注意:您可以使用主题的 default props 功能和 MuiUseMediaQuery 键来更改默认选项。

返回值

matches: 如果文档当前匹配媒体查询,则 Matches 为 true,否则为 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>;
}