自定义 Hook 开发
React 自定义 Hook 的概念、规则和常见实现
问题
什么是自定义 Hook?如何开发自定义 Hook?
解答
自定义 Hook 是以 use 开头的函数,内部可以调用其他 Hook,用于复用组件间的状态逻辑。
基本结构
// 自定义 Hook 必须以 use 开头
function useCustomHook(initialValue) {
const [state, setState] = useState(initialValue);
// 封装逻辑
const doSomething = useCallback(() => {
setState(/* ... */);
}, []);
// 返回状态和方法
return [state, doSomething];
}
示例 1:useToggle
import { useState, useCallback } from 'react';
// 布尔值切换 Hook
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return [value, toggle, setTrue, setFalse];
}
// 使用
function Modal() {
const [isOpen, toggle, open, close] = useToggle(false);
return (
<div>
<button onClick={open}>打开</button>
{isOpen && (
<div className="modal">
<button onClick={close}>关闭</button>
</div>
)}
</div>
);
}
示例 2:useLocalStorage
import { useState, useEffect } from 'react';
// 持久化状态到 localStorage
function useLocalStorage(key, initialValue) {
// 初始化时从 localStorage 读取
const [value, setValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
// 值变化时同步到 localStorage
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('localStorage 写入失败:', error);
}
}, [key, value]);
return [value, setValue];
}
// 使用
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
);
}
示例 3:useFetch
import { useState, useEffect } from 'react';
// 数据请求 Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error('请求失败');
const json = await response.json();
setData(json);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchData();
// 清理:取消未完成的请求
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// 使用
function UserList() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
示例 4:useDebounce
import { useState, useEffect } from 'react';
// 防抖 Hook
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// 清理上一次的定时器
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// 使用:搜索输入
function Search() {
const [keyword, setKeyword] = useState('');
const debouncedKeyword = useDebounce(keyword, 500);
// 只在防抖后的值变化时请求
const { data } = useFetch(
debouncedKeyword ? `/api/search?q=${debouncedKeyword}` : null
);
return (
<div>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="搜索..."
/>
{/* 渲染搜索结果 */}
</div>
);
}
关键点
- 命名规则:必须以
use开头,React 依此识别 Hook - 调用规则:只能在函数组件或其他 Hook 中调用,不能在条件语句中调用
- 状态隔离:每次调用自定义 Hook 都会获得独立的状态
- 返回值灵活:可返回数组、对象或单个值,根据使用场景决定
- 组合使用:自定义 Hook 内部可以调用其他 Hook,实现逻辑组合
目录