关于 react-hooks
面向 React 开发者的 Hooks 参考技能,涵盖内置 Hook、自定义 Hook 模式、规则与常见问题,帮助编写和排查组件逻辑。
主要功能
- 01内置 Hook 用途与时机参考
- 02自定义 Hook 常用模式
- 03Hooks 调用规则说明
- 04常见陷阱与问题排查
适用场景
- 01选择适合组件状态的 Hook
- 02实现数据获取或防抖逻辑
- 03排查依赖项与闭包问题
MCP 相关
文档建议通过 `mcp__documentation__fetch_docs` 获取 React Hooks 的详细资料。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill react-hooks安装后在 Claude Code 中输入/react-hooks确认已加载。
附带文件quick-ref/hooks-cheatsheet.md
详细介绍
React Hooks
深入了解:使用
mcp__documentation__fetch_docs,指定技术:react,主题:hooks,获取有关所有 React Hooks 和自定义 Hook 模式的完整文档。
内置 Hooks 参考
状态 Hooks
| Hook | 用途 | 使用时机 |
|---|---|---|
useState | 组件本地状态 | 简单的状态值 |
useReducer | 复杂的状态逻辑 | 包含多个子值或复杂更新 |
// useState - simple state
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
// useReducer - complex state
type Action = { type: 'increment' } | { type: 'decrement' } | { type: 'reset'; payload: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return action.payload;
}
}
const [count, dispatch] = useReducer(reducer, 0);
副作用 Hooks
| Hook | 用途 | 时机 |
|---|---|---|
useEffect | 副作用、订阅 | 绘制后 |
useLayoutEffect | DOM 测量 | 绘制前 |
useInsertionEffect | 注入 CSS-in-JS | DOM 变更前 |
// useEffect - most common
useEffect(() => {
const subscription = api.subscribe(id);
return () => subscription.unsubscribe(); // Cleanup
}, [id]); // Dependency array
// useLayoutEffect - measure DOM before paint
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setHeight(height);
}, []);
Context Hook
const ThemeContext = createContext<Theme>('light');
function Component() {
const theme = useContext(ThemeContext); // Reads nearest Provider value
return <div className={theme}>...</div>;
}
Ref Hooks
// DOM reference
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
// Mutable value (no re-render on change)
const renderCount = useRef(0);
renderCount.current++; // Doesn't cause re-render
// useImperativeHandle - customize ref exposed to parent
useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
scrollIntoView: () => inputRef.current?.scrollIntoView(),
}), []);
性能 Hooks
// useMemo - cache expensive calculations
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// useCallback - cache function references
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []); // Empty deps = stable reference
// useTransition - non-blocking state updates
const [isPending, startTransition] = useTransition();
startTransition(() => {
setSearchResults(filterLargeList(query)); // Won't block UI
});
// useDeferredValue - defer updating expensive UI
const deferredQuery = useDeferredValue(query);
// UI shows stale value while new value computes
其他 Hooks
// useId - unique IDs for accessibility
const id = useId();
<label htmlFor={id}>Email</label>
<input id={id} type="email" />
// useSyncExternalStore - subscribe to external stores
const state = useSyncExternalStore(
store.subscribe,
store.getSnapshot,
store.getServerSnapshot // Optional: for SSR
);
Hooks 规则
1. 只能在顶层调用
// BAD - conditional hook
if (condition) {
const [value, setValue] = useState(0); // ERROR
}
// BAD - hook in loop
for (const item of items) {
useEffect(() => {}, [item]); // ERROR
}
// GOOD - conditional logic inside hook
const [value, setValue] = useState(0);
useEffect(() => {
if (condition) {
// Do something
}
}, [condition]);
2. 只能在 React 函数中调用
// GOOD - in component
function Component() {
const [state, setState] = useState(0);
}
// GOOD - in custom hook
function useCustomHook() {
const [state, setState] = useState(0);
return state;
}
// BAD - regular function
function helper() {
const [state, setState] = useState(0); // ERROR
}
自定义 Hooks 模式
基本模式
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
// Usage
const [theme, setTheme] = useLocalStorage('theme', 'dark');
带加载/错误状态的获取 Hook
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
setData(await response.json());
} catch (e) {
setError(e instanceof Error ? e : new Error(String(e)));
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
防抖 Hook
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery);
}
}, [debouncedQuery]);
切换 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 };
}
// Usage
const { value: isOpen, toggle, setFalse: close } = useToggle();
上一个值 Hook
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Usage - detect changes
const prevCount = usePrevious(count);
useEffect(() => {
if (prevCount !== undefined && prevCount !== count) {
console.log(`Count changed from ${prevCount} to ${count}`);
}
}, [count, prevCount]);
媒体查询 Hook
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() =>
window.matchMedia(query).matches
);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [query]);
return matches;
}
// Usage
const isMobile = useMediaQuery('(max-width: 768px)');
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
常见陷阱
1. 缺少依赖项
// BAD - missing userId dependency
useEffect(() => {
fetchUser(userId);
}, []); // Lint error: userId missing
// GOOD
useEffect(() => {
fetchUser(userId);
}, [userId]);
2. 对象/数组依赖项
// BAD - new object every render
useEffect(() => {
doSomething(options);
}, [{ page: 1, limit: 10 }]); // Always triggers
// GOOD - use useMemo or primitive values
const options = useMemo(() => ({ page, limit }), [page, limit]);
useEffect(() => {
doSomething(options);
}, [options]);
3. 过期闭包
// BAD - stale count value
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // Always uses initial count
}, 1000);
return () => clearInterval(interval);
}, []); // count not in deps but used
// GOOD - functional update
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Uses current value
}, 1000);
return () => clearInterval(interval);
}, []);
不要使用此技能的情况
- React 19 的 use() Hook——改用
react-19技能 - Context API 实现——如需完整的 Context 模式,请使用
react-context技能 - 表单状态管理——改用
react-forms或react-hook-form技能 - 性能优化——如需详细的优化策略,请使用
react-performance技能
反模式
| 反模式 | 问题 | 解决方案 |
|---|---|---|
| 有条件地调用 Hooks | 破坏 Hook 顺序 | 始终在组件顶层调用 |
| 缺少依赖项 | 闭包过期 | 将所有依赖项包含在数组中 |
| 状态存在时使用空依赖数组 | 回调中的状态过期 | 使用函数式更新或添加依赖项 |
| 将对象/数组用作依赖项 | 每次都会触发 | 使用 useMemo 或单独的基本类型值 |
| 用 useEffect 派生状态 | 产生额外的渲染周期 | 在渲染期间计算 |
| 不清理副作用 | 内存泄漏 | 返回清理函数 |
| 自定义 Hook 未以 'use' 开头 | Linter 不会检查 | 将所有 Hooks 命名为 use* |
快速故障排除
| 问题 | 可能原因 | 修复方法 |
|---|---|---|
| Hook 顺序错误 | 有条件地调用 Hook | 将条件移入 Hook 内部 |
| 无限循环 | 依赖项缺失或错误 | 检查依赖项数组 |
| 闭包过期 | 缺少依赖项 | 添加到依赖项数组,或使用函数式更新 |
| 每次渲染都会运行副作用 | 缺少依赖项数组 | 添加 [] 或正确的依赖项 |
| 未运行清理 | 未返回函数 | 从 useEffect 返回清理函数 |
| useCallback 不起作用 | 创建了新的内联函数 | 检查依赖项数组 |
| useMemo 未进行记忆化 | 依赖项发生变化 | 检查依赖项是否稳定 |
参考
评论
支持 Markdown