React Hook 通过 自定义 Hook(Custom Hooks) 来处理代码复用,这是其最强大和优雅的特性之一。

在 Hook 出现之前,代码复用主要有两种方式,但它们都有明显的缺点:

  1. 高阶组件 (HOC - Higher-Order Components): 容易造成“嵌套地狱”(wrapper hell),组件结构不清晰,且逻辑来源难以追踪。
  2. Render Props: 虽然解决了嵌套问题,但会在组件内部形成回调地狱,同样降低了可读性。

自定义 Hook 如何解决这些问题?

一、核心思想:逻辑与UI分离,状态逻辑复用

自定义 Hook 的本质是将组件中可复用的状态逻辑提取到一个独立的函数中。这个函数内部可以调用其他 Hook(如 useState, useEffect 等),并且可以返回任何需要的值(状态、函数等)。


二、如何工作:一个详细的例子

假设我们有两个组件:一个显示用户列表,一个显示单个用户资料。它们都需要从 API 获取数据。没有复用时,每个组件都会重复写获取数据的逻辑(useState, useEffect)。

第1步:发现重复逻辑,将其提取为自定义 Hook

我们创建一个名为 useFetch 的 Hook来处理所有通用的数据获取逻辑。

// useFetch.js - 自定义 Hook
import { useState, useEffect } from 'react';

function useFetch(url) {
  // 1. 管理所有与数据获取相关的状态
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // 2. 数据获取的副作用逻辑
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await fetch(url);
        if (!response.ok) throw new Error('Network response was not ok');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]); // 3. 依赖项:当 url 改变时,会自动重新获取数据

  // 4. 返回组件可能需要的数据和方法
  return { data, loading, error };
}

export default useFetch;

第2步:在多个组件中复用这个逻辑

现在,任何需要从网络获取数据的组件都可以直接使用这个 useFetch Hook,而无需重复编写状态管理和 fetch 的逻辑。

  • 组件A:用户列表
// UserList.js
import useFetch from './useFetch';

function UserList() {
  // 使用自定义 Hook,传入需要的 URL
  const { data: users, loading, error } = useFetch('/api/users');

  if (loading) return <div>用户列表加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;

  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
  • 组件B:用户详情
// UserProfile.js
import useFetch from './useFetch';

function UserProfile({ userId }) {
  // 复用同一个 Hook,但传入不同的 URL
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>用户信息加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

三、自定义 Hook 的优势

  1. 逻辑与UI彻底分离: UI 组件只关心如何渲染,数据从哪里来由 Hook 负责。组件变得非常简洁和易读。
  2. 天然的解耦: 多个组件使用同一个 Hook 时,它们的 state 和 effect 是完全独立的UserListUserProfile 组件各自拥有自己的 data, loading, error 状态,不会互相影响。React 完全能区分开。
  3. 易于组合: 一个自定义 Hook 内部可以调用另一个自定义 Hook,可以构建出非常强大且清晰的逻辑链条。例如,一个 useUser Hook 内部可以调用 useFetch,然后再对获取到的用户数据进行一些加工。
  4. 清晰的数据流: 在组件中使用 Hook,可以非常清楚地知道数据(data)、状态(loading, error)来自哪里,不像 HOC 那样需要层层传递 props,逻辑来源模糊。
  5. 函数式的魅力: 它完全利用了 JavaScript 函数的特性(输入 => 输出),没有任何魔法般的组件包裹,只是纯粹的逻辑函数。

四、总结对比表

特性 高阶组件 (HOC) / Render Props 自定义 Hook
实现方式 组件包裹或函数回调 提取状态逻辑到函数
代码结构 容易产生嵌套地狱 扁平化,逻辑在函数内组合
可读性 较差,需要追踪逻辑来源 极好,逻辑来源清晰
性能 可能引入不必要的组件层级 无额外组件层级
调试难度 较难(React DevTools 中组件树很深) 容易(DevTools 中能看到 Hook 的调用)

结论:自定义 Hook 提供了了一种更直接、更符合函数式编程思想的代码复用方案,它让组件的关注点分离得更好,极大地提高了代码的可维护性和可复用性。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐