• 考点:可中断渲染、优先级调度、startTransition使用场景
• 示例:搜索框输入防抖优化
React Hooks 进阶:自定义 Hook 设计实战指南(以 useWindowSize
和 useFetch
为例)
一、自定义 Hook 设计规范
在实现 useWindowSize
和 useFetch
之前,需遵循以下核心设计原则:
- 单一职责原则:每个 Hook 仅解决一个特定问题(如窗口尺寸监听或数据请求)。
- 命名规范 :以
use
开头,采用驼峰命名法(如useWindowSize
)。 - 参数与返回值明确:函数签名需清晰描述输入/输出,支持 TypeScript 类型定义。
- 副作用管理:妥善处理事件监听、异步请求等副作用,确保组件卸载时清理资源。
- 可配置性 :通过参数暴露配置选项(如
useFetch
的请求 URL 和防抖时间)。
二、useWindowSize
实现与优化
1. 基础实现
tsx
import { useState, useEffect } from 'react';
type WindowSize = { width: number; height: number };
const useWindowSize = (): WindowSize => {
const [size, setSize] = useState<WindowSize>({
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;
};
// 使用示例
function App() {
const { width, height } = useWindowSize();
return <div>窗口尺寸:{width}px × {height}px</div>;
}
关键点 :
• 初始值直接读取 window.innerWidth/Height
,避免首次渲染时状态为 undefined
。
• 空依赖数组确保仅挂载时注册事件监听器,避免重复绑定。
2. 进阶优化
• SSR 兼容 :通过条件判断避免服务端渲染时访问 window
对象:
tsx
const [size, setSize] = useState<WindowSize>(() => ({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0,
}));
• 防抖处理:添加防抖函数减少高频触发:
tsx
useEffect(() => {
const debouncedResize = debounce(handleResize, 200);
window.addEventListener('resize', debouncedResize);
return () => window.removeEventListener('resize', debouncedResize);
}, []);
三、useFetch
实现与扩展
1. 基础实现(支持 TypeScript 泛型)
tsx
import { useState, useEffect } from 'react';
type FetchState<T> = {
data: T | null;
loading: boolean;
error: Error | null;
};
const useFetch = <T,>(url: string): FetchState<T> => {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP Error ${response.status}`);
const data = await response.json() as T;
setState({ data, loading: false, error: null });
} catch (error) {
if (!controller.signal.aborted) {
setState({ data: null, loading: false, error: error as Error });
}
}
};
fetchData();
return () => controller.abort();
}, [url]);
return state;
};
// 使用示例
interface User { id: number; name: string; }
const UserList = () => {
const { data, loading, error } = useFetch<User[]>('/api/users');
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error.message}</div>;
return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
};
关键点 :
• 使用 AbortController
取消未完成的请求,避免组件卸载后更新状态。
• 泛型 <T>
支持类型推断,确保返回数据与接口定义一致。
2. 扩展功能
• 请求参数配置化 :支持 method
、headers
等配置项:
tsx
type FetchOptions = RequestInit & { debounce?: number };
const useFetch = <T,>(url: string, options?: FetchOptions): FetchState<T> => {
// 扩展配置逻辑
};
• 自动重试机制:添加重试次数和间隔配置:
tsx
const fetchData = async (retryCount = 0) => {
try {
// 请求逻辑...
} catch (error) {
if (retryCount < 3) {
setTimeout(() => fetchData(retryCount + 1), 1000);
}
}
};
四、自定义 Hook 最佳实践
-
逻辑复用与组件拆分
• 将业务逻辑(如数据请求)与 UI 组件分离,提升可维护性。
• 示例:
useFetch
处理数据获取,<DataTable>
组件仅负责渲染。 -
性能优化策略
• 避免过度渲染:在
useFetch
中通过useMemo
缓存计算结果。• 请求防抖/节流:减少无效 API 调用(如搜索框输入场景)。
-
错误边界处理
• 在
useFetch
中统一捕获异常,并通过返回值传递错误信息。• 使用
ErrorBoundary
组件捕获子组件抛出的错误。 -
TypeScript 深度集成
• 为自定义 Hook 添加完善的类型定义,支持泛型和类型推断。
五、总结对比
Hook 类型 | 核心功能 | 关键技术点 | 典型应用场景 |
---|---|---|---|
useWindowSize |
监听窗口尺寸变化 | 事件监听、防抖、SSR 兼容 | 响应式布局、图表自适应 |
useFetch |
封装数据请求逻辑 | AbortController、泛型、错误处理 | 表格加载、搜索接口调用 |
设计哲学 :通过自定义 Hook 将 React 的声明式特性与命令式逻辑结合,实现 逻辑与 UI 解耦,同时遵循 React Hooks 的设计规则(如仅在顶层调用)。