Featured image of post 设计干净的 React 自定义 Hook 以实现可重用性Featured image of post 设计干净的 React 自定义 Hook 以实现可重用性

设计干净的 React 自定义 Hook 以实现可重用性

了解如何使用 React Custom Hooks (useXXX) 将业务逻辑和状态管理与视图层分离,以提高代码可维护性。

介绍

当您开发 React 组件时,您可能会发现文件增长到数百行。当 useStateuseEffect、API 查询和验证逻辑与渲染标记混合时,就会发生这种情况,使组件难以阅读和维护。

这种关注点的混合使得编写单元测试变得困难,并且在跨不同视图重用逻辑时增加了复制粘贴错误的风险。

在 React 中将 UI(表示)与业务逻辑(行为)分离的最佳方法是创建 自定义 Hooks。本指南概述了构建可重复使用的自定义挂钩的核心设计指南。


1. 什么是自定义 Hook?

自定义挂钩是一个标准 JavaScript 函数,其名称 以前缀 use 开头。在自定义挂钩中,您可以调用标准 React 挂钩,例如 useStateuseEffectuseContext

定制挂钩的优点

  • **干净的组件(关注点分离):**组件严格专注于声明可视化 UI 结构(JSX),而数据获取、状态更新和副作用则封装在钩子内。
  • 逻辑可重用性: 在多个同级组件之间共享辅助逻辑(如窗口大小观察器、表单管理或获取请求包装器),而无需重复代码。

2.重构示例:提取自定义hook

让我们看一个示例:一个从 API 获取用户列表并将其显示在屏幕上的组件。

重构前(UI 与逻辑混合)

import { useState, useEffect } from 'react';

export function UserList() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setIsLoading(false);
      })
      .catch(err => {
        setError(err);
        setIsLoading(false);
      });
  }, []);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error occurred.</p>;

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}
  • 问题: 该组件处理 API URL、请求逻辑、错误跟踪、加载指示器和 JSX 渲染。这使得独立测试 UI 和数据获取逻辑变得困难。

重构后(提取 useUsers

首先,将数据获取逻辑隔离到一个单独的文件中,作为名为 useUsers 的自定义挂钩:

// useUsers.js
import { useState, useEffect } from 'react';

export function useUsers() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let active = true;
    fetch('https://api.example.com/users')
      .then(res => res.json())
      .then(data => {
        if (active) {
          setUsers(data);
          setIsLoading(false);
        }
      })
      .catch(err => {
        if (active) {
          setError(err);
          setIsLoading(false);
        }
      });

    // Clean up to prevent race conditions
    return () => { active = false; };
  }, []);

  // Return only the data and states required by components
  return { users, isLoading, error };
}

现在,更新组件以使用自定义挂钩:

// UserList.jsx
import { useUsers } from './useUsers';

export function UserList() {
  // Access data and state via the custom hook
  const { users, isLoading, error } = useUsers();

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error occurred.</p>;

  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}
  • 结果: 组件现在变得精简且可读。它不需要知道 API 请求是如何配置的,您可以为 UI 组件和自定义挂钩编写独立的测试。

3. 设计自定义 Hook 的三个规则

规则 1:不返回 JSX(HTML 标记)

自定义挂钩应该管理逻辑状态,而不是返回可视组件。返回对象、数组、变量或回调函数而不是 JSX 元素(如 <div />),以保持钩子的灵活性。

规则 2:保持返回接口较小

仅返回组件管理其渲染生命周期所需的变量。避免返回钩子内声明的每个内部状态变量。

规则 3:嵌套和组合 Hook

您可以在您的挂钩中调用其他自定义挂钩。例如,您可以在 useUsers 内部调用通用 useFetch 挂钩,以进一步模块化您的代码。

结论

React 自定义挂钩有助于保持组件的可读性和可测试性:

  1. 将数据获取和状态管理逻辑提取到以 use 为前缀的函数中。
  2. 封装副作用和获取状态,仅返回 UI 组件需要的变量。
  3. 处理钩子内的清理逻辑,以防止内存泄漏和竞争条件。

采用这些自定义钩子设计模式来构建更易于维护的 React 应用程序。