React 学习指南 - 第十八天 🚀
主题:自定义 Hooks(封装可复用逻辑)
在 React 中,自定义 Hooks 是一个非常强大的工具,它能让我们复用逻辑,减少代码冗余,提高可读性和维护性。今天,我们将深入学习 如何创建自定义 Hooks,并探讨几个常见的实用场景。
1. 为什么需要自定义 Hooks?
在开发 React 应用时,我们经常会遇到相似的逻辑,例如:
获取和存储
localStorage
数据处理窗口大小、鼠标位置等浏览器事件
请求 API 获取数据
防抖(debounce)和节流(throttle)
如果不使用自定义 Hooks,我们可能会在多个组件中重复编写相同的逻辑,导致代码冗余、难以维护。
自定义 Hooks 可以将这些逻辑封装起来,使代码更加模块化、易读、易测试。
2. 创建第一个自定义 Hook
自定义 Hooks 本质上就是一个以 use
开头的 JavaScript 函数,它可以使用其他 Hooks,但必须遵循 React Hooks 规则。
示例 1:封装 useLocalStorage
很多应用都需要存储用户偏好(如主题、语言)到 localStorage
。
我们可以创建一个 useLocalStorage
Hook 来管理 localStorage
的读写。
import { useState, useEffect } from "react"; function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { // 读取 localStorage const storedValue = localStorage.getItem(key); return storedValue ? JSON.parse(storedValue) : initialValue; }); useEffect(() => { // 更新 localStorage localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; } // 使用自定义 Hook function App() { const [theme, setTheme] = useLocalStorage("theme", "light"); return ( <div> <p>当前主题:{theme}</p> <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> 切换主题 </button> </div> ); } export default App;
✅ 数据持久化:刷新页面后,theme
仍然保留
✅ 封装了 localStorage
逻辑,可以在不同组件复用
✅ useEffect 监听 value
变化,自动更新 localStorage
3. 监听窗口大小:useWindowSize
在响应式设计中,我们可能需要监听窗口大小的变化,并动态调整 UI。
import { useState, useEffect } from "react"; function useWindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); return size; } // 使用自定义 Hook function App() { const { width, height } = useWindowSize(); return ( <div> <p>窗口宽度:{width}px</p> <p>窗口高度:{height}px</p> </div> ); } export default App;
✅ 监听 resize
事件,窗口大小变化时自动更新状态
✅ 自动解绑事件监听器,避免内存泄漏
4. 获取鼠标位置:useMousePosition
如果我们需要追踪鼠标位置(如画布、游戏、交互组件),可以创建 useMousePosition
Hook。
import { useState, useEffect } from "react"; function useMousePosition() { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const updateMousePosition = (e) => { setPosition({ x: e.clientX, y: e.clientY }); }; window.addEventListener("mousemove", updateMousePosition); return () => window.removeEventListener("mousemove", updateMousePosition); }, []); return position; } // 使用自定义 Hook function App() { const { x, y } = useMousePosition(); return ( <div> <p>鼠标位置:X={x}, Y={y}</p> </div> ); } export default App;
✅ 自动监听鼠标移动,适用于绘图、拖拽、交互组件
✅ 自动解绑监听事件,避免性能问题
5. API 请求封装:useFetch
在 React 应用中,我们通常需要请求 API 并管理加载状态和错误处理。useFetch
可以封装这一逻辑,使其更易复用。
import { useState, useEffect } from "react"; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); fetch(url) .then((response) => response.json()) .then((data) => { setData(data); setLoading(false); }) .catch((err) => { setError(err); setLoading(false); }); }, [url]); return { data, loading, error }; } // 使用自定义 Hook function App() { const { data, loading, error } = useFetch("https://jsonplaceholder.typicode.com/posts/1"); if (loading) return <p>加载中...</p>; if (error) return <p>错误:{error.message}</p>; return ( <div> <h2>{data.title}</h2> <p>{data.body}</p> </div> ); } export default App;
✅ 封装了 API 请求、加载状态、错误处理
✅ 复用性强,适用于所有 API 请求
✅ 通过依赖 url
实现自动请求
6. 防抖与节流
防抖(Debounce):useDebounce
防抖用于减少输入频率,例如搜索框防止频繁请求 API。
import { useState, useEffect } from "react"; function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => clearTimeout(handler); }, [value, delay]); return debouncedValue; } // 使用自定义 Hook function App() { const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 500); useEffect(() => { if (debouncedQuery) { console.log("搜索:" + debouncedQuery); } }, [debouncedQuery]); return ( <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="输入搜索内容..." /> ); }
✅ 减少 API 请求频率,适用于搜索框、输入框
本文来自E先生的博客,如若转载,请注明出处:https://javajz.cn
留言区