当前位置: 首页 > news >正文

第六节:React Hooks进阶篇-自定义Hook设计

实战题:实现一个useWindowSize或useFetch

自定义 Hook 设计实战:useWindowSizeuseFetch 实现详解


一、useWindowSize:实时监听窗口尺寸
1. 基础实现(TypeScript 版)
import { useState, useEffect } from 'react';// 定义返回值的类型
type WindowSize = {width: number;height: number;
};const useWindowSize = (): WindowSize => {// 处理 SSR 环境(如 Next.js)const isSSR = typeof window === 'undefined';// 初始化状态(兼容 SSR)const [windowSize, setWindowSize] = useState<WindowSize>(() => ({width: isSSR ? 0 : window.innerWidth,height: isSSR ? 0 : window.innerHeight,}));useEffect(() => {if (isSSR) return; // 服务端无需执行const handleResize = () => {setWindowSize({width: window.innerWidth,height: window.innerHeight,});};// 立即触发一次以获取初始值handleResize();// 添加事件监听window.addEventListener('resize', handleResize);// 清理函数:移除监听return () => {window.removeEventListener('resize', handleResize);};}, [isSSR]); // 依赖项为 isSSR(仅挂载时执行)return windowSize;
};// 使用示例
const ResponsiveComponent = () => {const { width, height } = useWindowSize();return <div>当前窗口尺寸:{width}px × {height}px</div>;
};
2. 进阶优化:防抖处理
// 防抖工具函数
const debounce = (fn: Function, delay: number) => {let timer: NodeJS.Timeout;return (...args: any[]) => {clearTimeout(timer);timer = setTimeout(() => fn(...args), delay);};
};// 修改 useEffect 部分
useEffect(() => {// ...其他代码同前const debouncedResize = debounce(handleResize, 200);window.addEventListener('resize', debouncedResize);return () => window.removeEventListener('resize', debouncedResize);
}, [isSSR]);

二、useFetch:通用数据请求 Hook
1. 完整实现(支持 TypeScript 泛型)
import { useState, useEffect } from 'react';// 定义状态类型
type FetchState<T> = {data: T | null;isLoading: boolean;error: Error | null;
};// 请求配置类型
type FetchOptions = RequestInit & {timeout?: number;
};const useFetch = <T,>(url: string,options?: FetchOptions
): FetchState<T> => {const [state, setState] = useState<FetchState<T>>({data: null,isLoading: true,error: null,});useEffect(() => {const controller = new AbortController();const { signal } = controller;let timeoutId: NodeJS.Timeout;const fetchData = async () => {try {// 设置超时if (options?.timeout) {timeoutId = setTimeout(() => {controller.abort();setState({data: null,isLoading: false,error: new Error('请求超时'),});}, options.timeout);}const response = await fetch(url, {...options,signal,});if (!response.ok) {throw new Error(`HTTP 错误 ${response.status}`);}const data: T = await response.json();setState({ data, isLoading: false, error: null });} catch (error) {// 忽略已取消的请求错误if (error.name !== 'AbortError') {setState({data: null,isLoading: false,error: error as Error,});}} finally {clearTimeout(timeoutId);}};fetchData();// 清理函数:取消请求return () => {controller.abort();clearTimeout(timeoutId);};}, [url, options?.timeout]); // 依赖项:URL 和超时时间return state;
};// 使用示例
interface User {id: number;name: string;
}const UserList = () => {const { data, isLoading, error } = useFetch<User[]>('/api/users', {timeout: 5000,});if (isLoading) return <div>加载中...</div>;if (error) return <div>错误:{error.message}</div>;return (<ul>{data?.map((user) => (<li key={user.id}>{user.name}</li>))}</ul>);
};
2. 关键功能解析
特性实现方式
请求取消AbortController + signal 参数
超时处理setTimeout + 自动取消请求
类型安全泛型 <T> 定义响应数据类型
错误处理区分网络错误、HTTP 状态码错误、超时错误
SSR 兼容无需特殊处理(浏览器 API 在服务端不会执行)

三、设计原则与最佳实践
1. 通用 Hook 设计规范

单一职责
useWindowSize 只关注窗口尺寸变化
useFetch 仅处理数据请求生命周期

明确输入输出

// useWindowSize:无需参数,返回尺寸对象
const { width } = useWindowSize();// useFetch:接收 URL 和配置,返回状态对象
const { data } = useFetch<User>('/api/user');

副作用清理

// 组件卸载时移除监听器/取消请求
useEffect(() => () => {window.removeEventListener('resize', handleResize);controller.abort();
}, []);
2. 性能优化策略

防抖/节流

// 避免 resize 事件高频触发状态更新
const debouncedResize = debounce(handleResize, 200);

条件执行

// 仅在 URL 变化时发起新请求
useEffect(() => {if (!url) return;fetchData();
}, [url]);

记忆化配置

// 使用 useMemo 避免重复触发请求
const options = useMemo(() => ({ timeout: 5000 }), []);
const { data } = useFetch(url, options);

四、对比总结
Hook核心场景关键技术点优化方向
useWindowSize响应式布局、图表自适应事件监听、SSR 兼容、防抖动态阈值判断(如移动端横竖屏)
useFetch数据列表加载、表单提交请求取消、错误处理、泛型支持缓存策略(SWR/React Query)

五、实战技巧
  1. 组合使用 Hooks

    // 响应式布局 + 数据加载
    const { width } = useWindowSize();
    const isMobile = width < 768;
    const { data } = useFetch<Article[]>(isMobile ? '/mobile-api' : '/desktop-api');
    
  2. 扩展 useFetch
    • 添加重试机制
    • 支持 JWT 自动刷新
    • 集成全局 Loading 状态管理

  3. 测试策略
    • 使用 Jest 模拟 window.resize 事件
    • 利用 msw 模拟 API 请求

通过合理设计自定义 Hook,可以显著提升代码复用率与可维护性。建议将通用逻辑抽象为 Hook,并通过文档明确使用约束。

相关文章:

  • 大模型时代下全场景数据消费平台的智能BI—Quick BI深度解析
  • 【数字图像处理】图像增强
  • King3399(ubuntu文件系统)GDB/GDBServer调试配置
  • 《Cangjie Magic实战手记:用Agent DSL与MCP协议重构智能物流调度系统》——一次从技术困惑到行业落地的探索之旅
  • 当 AI 有了 “万能插头” 和 “通用语言”:MCP 与 A2A 如何重构智能体生态
  • 【JAVA】在idea新加artifact时,点击Build-Build Artifacts时,新加的artifact不能选中
  • Java NIO Java 虚拟线程(微线程)与 Go 协程的运行原理不同 为何Go 能在低配机器上承接10万 Websocket 协议连接
  • 吊顶上的灯线怎么预留?是提前到位还是后期随意拉拽?
  • AI 驱动下的后端开发架构革命:从智能协同体系
  • golang处理时间的包time一次性全面了解
  • 岚图L3智能架构发布,9大首发新技术引领电动车变革
  • git更新的bug
  • C/C++语言常见问题-智能指针、多态原理
  • ES|QL,知道吗,专为搜索而生 —— 推出评分和语义搜索
  • Elasticsearch的Java客户端库QueryBuilders查询方法大全
  • 【Amazon 工具】在MacOS本地安装 AWS CLI、kubectl、eksctl工具
  • 【Windows上配置Git环境】
  • 关于 AI驱动的智慧家居、智慧城市、智慧交通、智慧医疗和智慧生活 的详细解析,涵盖其定义、核心技术、应用场景、典型案例及未来趋势
  • AI与物联网的深度融合:开启智能生活新时代
  • 健康养生:开启活力生活的密钥
  • 历史缝隙里的人︱觑功名如画饼:盛世“做题家”的攀爬与坠落
  • 意德首脑会谈,梅洛尼警告欧盟绿色政策面临“工业荒漠化”
  • 15年全免费,内蒙古准格尔旗实现幼儿园到高中0学费
  • 夜读丨什么样的前程值得把春天错过
  • 马上评|家长抱婴儿值护学岗,如何避免“被自愿”?
  • 梅花奖在上海|话剧《主角》:艺术与人生的交错