如何在 React 逻辑中使用自定义 Hook 处理 Media Queries

发布: (2025年12月11日 GMT+8 09:05)
4 min read
原文: Dev.to

Source: Dev.to

介绍

CSS 媒体查询在样式上非常强大——可以根据屏幕宽度改变字体大小、内边距或网格布局。
但当你需要改变 逻辑结构 时该怎么办?

举例来说,在桌面端你可能想要在左侧保持一个持久的 Sidebar,而在移动端同样的侧边栏需要变成一个在点击按钮时弹出的 SheetModal。仅靠 display: none 是做不到的,因为组件的行为不同。这时就需要使用 JavaScript 媒体查询。

在本文中,我将分享一个轻量级的自定义 Hook useMediaQuery,让你可以直接在 React 组件内部检测屏幕尺寸。

“仅 CSS 隐藏” 的问题

一个常见的错误是同时渲染移动端和桌面端组件,然后用 CSS 隐藏其中一个。

{/* This is bad for performance! */}

这种做法会导致 DOM 膨胀,因为 React 仍然会渲染两个组件、执行它们的副作用和逻辑,即使用户看不到它们。

解决方案:useMediaQuery Hook

通过在 React Hook 中使用原生的 window.matchMedia API,我们可以高效地跟踪屏幕状态。

import { useEffect, useState } from "react";

export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    const media = window.matchMedia(query);

    // Update state immediately if it doesn't match
    if (media.matches !== matches) {
      setMatches(media.matches);
    }

    // Listener for subsequent changes
    const listener = () => setMatches(media.matches);

    // Modern browsers use addEventListener for matchMedia
    media.addEventListener("change", listener);

    // Clean up the listener on unmount
    return () => media.removeEventListener("change", listener);
  }, [matches, query]);

  return matches;
}

工作原理

  • window.matchMedia(query) – 检查文档是否匹配媒体查询字符串(例如 (max-width: 768px))。
  • useState – 将结果(truefalse)存入本地状态。
  • 事件监听器 – 监听 change 事件;当用户调整窗口大小时,状态会自动更新。

实际案例:Sidebar 与 Sheet

现在,看看如何使用这个 Hook 来解决 “Sidebar 与 Sheet” 的问题。我们希望根据设备渲染完全不同的 UI 结构。

import { useState } from "react";
import { useMediaQuery } from "./hooks/use-media-query";

export function CourseSidebar({
  course,
  currentLessonId,
  onLessonSelect,
  collapsed = false,
  onToggle,
}: Props) {
  const [isSheetOpen, setIsSheetOpen] = useState(false);

  // Define your breakpoint here
  const isMobile = useMediaQuery("(max-width: 768px)");

  // 1. Render Mobile View (Sheet/Modal)
  if (isMobile) {
    return (
      <Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
        <SheetTrigger>
          <Button>Menu</Button>
        </SheetTrigger>
        <SheetContent>
          {/* Your Menu Items */}
        </SheetContent>
      </Sheet>
    );
  }

  // 2. Render Desktop View (Standard Sidebar)
  return (
    <aside>
      {/* Your Menu Items */}
    </aside>
  );
}

为什么这样更好

通过使用 if (isMobile),我们实现了 条件渲染

  • 在移动端,桌面侧边栏根本不会被渲染到 DOM 中。
  • 在桌面端,移动端的 Sheet 代码则被忽略。

这让应用更轻量、更快,并且避免了在桌面视图时移动端事件监听器仍然触发的 bug。

总结

CSS 用于样式;JavaScript 用于逻辑。当你需要根据视口改变渲染内容时,使用 window.matchMedia。这个简洁的 useMediaQuery Hook 能够把 CSS 断点和 React 组件逻辑桥接起来。

欲了解更多信息,请访问 my website

Back to Blog

相关文章

阅读更多 »

内部实现:React

介绍 我自从开始使用 React 的那一刻起就想做这件事:了解它的运行机制。这不是对源代码的细粒度审查。在…

React 使用计算器

今天我完成了一个使用 React 的练习项目——计算器。这个 React Calculator 应用程序执行基本的算术运算。它支持按钮…