第七节:React HooksReact 18+新特性-并发模式(Concurrent Mode)解决了什么问题?

• 考点:可中断渲染、优先级调度、startTransition使用场景

• 示例:搜索框输入防抖优化

React Hooks 进阶:自定义 Hook 设计实战指南(以 useWindowSizeuseFetch 为例)


一、自定义 Hook 设计规范

在实现 useWindowSizeuseFetch 之前,需遵循以下核心设计原则:

  1. 单一职责原则:每个 Hook 仅解决一个特定问题(如窗口尺寸监听或数据请求)。
  2. 命名规范 :以 use 开头,采用驼峰命名法(如 useWindowSize)。
  3. 参数与返回值明确:函数签名需清晰描述输入/输出,支持 TypeScript 类型定义。
  4. 副作用管理:妥善处理事件监听、异步请求等副作用,确保组件卸载时清理资源。
  5. 可配置性 :通过参数暴露配置选项(如 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. 扩展功能

请求参数配置化 :支持 methodheaders 等配置项:

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 最佳实践
  1. 逻辑复用与组件拆分

    • 将业务逻辑(如数据请求)与 UI 组件分离,提升可维护性。

    • 示例:useFetch 处理数据获取,<DataTable> 组件仅负责渲染。

  2. 性能优化策略

    • 避免过度渲染:在 useFetch 中通过 useMemo 缓存计算结果。

    • 请求防抖/节流:减少无效 API 调用(如搜索框输入场景)。

  3. 错误边界处理

    • 在 useFetch 中统一捕获异常,并通过返回值传递错误信息。

    • 使用 ErrorBoundary 组件捕获子组件抛出的错误。

  4. TypeScript 深度集成

    • 为自定义 Hook 添加完善的类型定义,支持泛型和类型推断。


五、总结对比
Hook 类型 核心功能 关键技术点 典型应用场景
useWindowSize 监听窗口尺寸变化 事件监听、防抖、SSR 兼容 响应式布局、图表自适应
useFetch 封装数据请求逻辑 AbortController、泛型、错误处理 表格加载、搜索接口调用

设计哲学 :通过自定义 Hook 将 React 的声明式特性与命令式逻辑结合,实现 逻辑与 UI 解耦,同时遵循 React Hooks 的设计规则(如仅在顶层调用)。

相关推荐
恋猫de小郭4 分钟前
Dart 3.13 的到底改了什么?为什么很重要?有什么坑?
android·前端·flutter
这是个栗子9 分钟前
前端开发中的常用工具函数(十):JavaScript 数组遍历
javascript·map·遍历·filter·foreach·for·for...of
FYKJ_201017 分钟前
django学习成绩预警系统10905
java·javascript·spring boot·python·spark·django·php
云浪18 分钟前
给大模型装上"手":三分钟看懂 AI Agent 的 Tool 工具调用
javascript·人工智能·node.js
风月说与山鬼19 分钟前
六、React事件
前端·javascript·react.js
岁岁种桃花儿20 分钟前
Vue核心语法第十四篇:列表过滤
前端·javascript·vue.js
晓得迷路了20 分钟前
栗子前端技术周刊第 143 期 - npm 攻击事件、Bun 1.4...
前端·javascript
YHHLAI20 分钟前
实战 Vibe Coding:用 AI 从零搭一个 React 待办清单
前端·人工智能·react.js
常宇佳27 分钟前
vue3 实战系列之踩过的一些坑
javascript·vue.js·elementui
zzzzzz3102 小时前
别急着把动效组件搬进页面:从 react-bits 看 React 动效库该怎么评估
前端·react.js·开源