如何在 React 逻辑中使用自定义 Hook 处理 Media Queries
Source: Dev.to
介绍
CSS 媒体查询在样式上非常强大——可以根据屏幕宽度改变字体大小、内边距或网格布局。
但当你需要改变 逻辑 或 结构 时该怎么办?
举例来说,在桌面端你可能想要在左侧保持一个持久的 Sidebar,而在移动端同样的侧边栏需要变成一个在点击按钮时弹出的 Sheet 或 Modal。仅靠 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– 将结果(true或false)存入本地状态。- 事件监听器 – 监听
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。