Hook组合的艺术:react-custom-hooks的useDarkMode如何组合useMediaQuery与useLocalStorage
【免费下载链接】react-custom-hooksReact Custom Hooks @ S.Leschev: useArray useAsync useClickOutside useCookie useCopyToClipboard useDarkMode useDebounce useDebugInformation useDeepCompareEffect useEffectOnce useEventListener useFetch useGeolocation useHover useLongPress useMediaQuery useOnlineStatus useOnScreen usePrevious useRenderCount useScript etc.项目地址: https://gitcode.com/gh_mirrors/re/react-custom-hooks
🌙React 自定义 Hook是复用状态逻辑的最佳方式。在 react-custom-hooks 开源项目中,useDarkMode是教科书级的组合范例:它把useMediaQuery(监听系统深色模式偏好)和useLocalStorage(记住用户手动选择)两个基础 Hook 巧妙拼装在一起,仅用十几行代码就实现了"跟随系统 + 可手动切换 + 刷新不丢失"的完整深色模式功能。本文将带你理解这个 React 自定义 Hook 组合的设计思路。
🎯 先搞懂:什么是 React 自定义 Hook
React 16.8 引入 Hooks 后,开发者可以用useState、useEffect在函数组件中管理状态。而自定义 Hook则是"用 Hook 写 Hook"——以use开头命名,把复杂逻辑封装成可复用的函数。
react-custom-hooks 项目提供了 20+ 个生产级自定义 Hook:useArray、useAsync、useDebounce、useFetch、useGeolocation等,覆盖状态管理、事件监听、存储、媒体查询等常见场景。其中useDarkMode最能体现Hook 组合的艺术:一个 Hook 调用另外两个 Hook,层层组合,各司其职。
🔍 三大 Hook 分工:一个"功能金字塔"
useDarkMode的底层由 3 个 Hook 支撑,形成清晰的三层结构:
| 层级 | Hook | 职责 | 源码位置 |
|---|---|---|---|
| 顶层 | useDarkMode | 组合决策,输出最终开关状态 | src/hooks/useDarkMode/useDarkMode.js |
| 中层 | useMediaQuery | 监听系统prefers-color-scheme变化 | src/hooks/useMediaQuery/useMediaQuery.js |
| 中层 | useLocalStorage | 持久化用户手动选择 | src/hooks/useStorage/useStorage.js |
| 底层 | useEventListener | 为useMediaQuery提供事件绑定能力 | src/hooks/useEventListener/useEventListener.js |
💡 注意一个细节:项目里并没有独立的
useLocalStorage文件,useLocalStorage是useStorage模块对外导出的工厂函数(见src/hooks/useStorage/useStorage.js),同时它还导出了useSessionStorage,一份代码两种存储。
🧩 组合逻辑拆解:一行代码决定最终状态
打开src/hooks/useDarkMode/useDarkMode.js,核心逻辑只有 5 行:
const [darkMode, setDarkMode] = useLocalStorage("useDarkMode") const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)") const enabled = darkMode ?? prefersDarkMode这段代码的设计精髓在于??(空值合并运算符):
- 用户手动切换过→
darkMode有值(true或false),以用户选择为准; - 用户从未操作→
darkMode为null,自动降级到系统偏好prefersDarkMode。
接下来通过useEffect把状态同步到<body>:
useEffect(() => { document.body.classList.toggle("dark-mode", enabled) }, [enabled])配套的样式只需一行 CSS(见src/hooks/useDarkMode/body.css):
body.dark-mode { background-color: #333; }⚙️ 底层如何实时响应系统变化?
useMediaQuery的巧妙之处在于:它先通过window.matchMedia拿到 MediaQueryList 对象,再借助useEventListener监听其change事件,从而在系统主题切换的瞬间触发组件重渲染。
这里useEventListener用useRef保存最新回调,避免了"闭包陷阱"——事件处理器不会因为组件重渲染而失效或重复绑定。这正是 Hook 组合能层层叠加的底气:每一层都只做一件事,且接口干净。
🚀 新手上手:3 步接入 useDarkMode
第 1 步:克隆项目到本地(仓库地址:https://gitcode.com/gh_mirrors/re/react-custom-hooks)。
第 2 步:在任意组件中引入 Hook,参考官方示例组件src/hooks/useDarkMode/DarkModeComponent.js的用法:
import useDarkMode from "./useDarkMode" function App() { const [darkMode, setDarkMode] = useDarkMode() return <button onClick={() => setDarkMode(!darkMode)}>切换主题</button> }第 3 步:setDarkMode支持函数式更新,切换逻辑会写入localStorage,刷新页面后依然保留 👍
💡 为什么这个组合模式值得学习
- 关注点分离:媒体查询、持久化、DOM 同步各在一个 Hook 里,单独可测试、可复用;
- 零重复代码:
useMediaQuery和useLocalStorage本身就是通用 Hook,换个项目直接搬走; - 优雅降级:
??让"用户优先、系统兜底"的策略只花一行代码实现; - 状态即 API:返回
[enabled, setDarkMode]数组,与useState签名一致,上手零成本。
📌 小结
useDarkMode展示了 React 自定义 Hook 的黄金法则:Hook 是可以嵌套组合的乐高积木。想在自己的项目中实现深色模式?不用引入任何状态管理库,复制src/hooks/useDarkMode/useDarkMode.js、src/hooks/useMediaQuery/useMediaQuery.js、src/hooks/useStorage/useStorage.js、src/hooks/useEventListener/useEventListener.js四个文件即可开工。掌握这种组合思维,你就能用基础 Hook 拼出useOnlineStatus、useWindowSize等任何"监听 + 存储 + 副作用"三件套功能。
【免费下载链接】react-custom-hooksReact Custom Hooks @ S.Leschev: useArray useAsync useClickOutside useCookie useCopyToClipboard useDarkMode useDebounce useDebugInformation useDeepCompareEffect useEffectOnce useEventListener useFetch useGeolocation useHover useLongPress useMediaQuery useOnlineStatus useOnScreen usePrevious useRenderCount useScript etc.项目地址: https://gitcode.com/gh_mirrors/re/react-custom-hooks
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考