深入 React 自定义 Hook:提升代码复用与逻辑组织能力

110 阅读3分钟

在 React 开发中,自定义 Hook 是一种非常强大的机制,它允许我们将组件中的逻辑抽取出来,封装成可复用的函数。这种模式不仅让组件更简洁,也极大提升了代码的可维护性和可测试性。

本文将带你从基础概念到实际应用,全面了解 React 自定义 Hook 的使用方式、适用场景以及最佳实践。

一、什么是自定义 Hook?

自定义Hook是一个以use开头的JavaScript函数,他可以在内部使用其他内置的Hook(如useStateuseEffect等),用于封装和共享组件之间的状态逻辑

注意:自定义 Hook 不共享状态本身,而是共享状态逻辑。

二、为什么使用自定义 Hook?

在函数组件中,我们可以通过 Hook 来管理状态和副作用。但如果你在多个组件中重复使用相似的逻辑(如数据获取、表单处理、事件订阅等),就会导致代码重复和难以维护。

此时,自定义 Hook 就派上用场了:

  • ✅ 逻辑复用:避免在多个组件中重复编写相同的逻辑。
  • ✅ 组件瘦身:将复杂逻辑从组件中抽离,保持组件清晰。
  • ✅ 提高可测试性:逻辑封装成函数后,更容易进行单元测试。
  • ✅ 增强可维护性:一处修改,多处生效。

三、自定义 Hook 的基本结构

一个自定义 Hook 本质上是一个函数,命名以 use 开头:

function useSomething() {
  // 可以使用 useState, useEffect 等
  return something; // 返回状态、函数或其他值
}

示例:封装一个计数器逻辑

import { useState, useCallback } from 'react';

function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);

  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);

  return { count, increment, decrement };
}

在组件中使用:

function CounterComponent() {
  const { count, increment, decrement } = useCounter(0);

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
}

四、自定义 Hook 的使用场景

1. 数据获取(Data Fetching)

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

使用示例:

function UserList() {
  const { data, loading, error } = useFetch('https://api.example.com/users');

  if (loading) return <p>加载中...</p>;
  if (error) return <p>出错了!{error.message}</p>;

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. 表单管理

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };

  const reset = () => setValues(initialValues);

  return { values, handleChange, reset };
}

使用示例:

function LoginForm() {
  const { values, handleChange } = useForm({ username: '', password: '' });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交数据:', values);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="username" value={values.username} onChange={handleChange} placeholder="用户名" />
      <input name="password" type="password" value={values.password} onChange={handleChange} placeholder="密码" />
      <button type="submit">登录</button>
    </form>
  );
}

3. 监听窗口大小变化

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const onResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  return size;
}

使用示例:

function ResponsiveComponent() {
  const { width, height } = useWindowSize();

  return (
    <div>
      当前窗口大小: {width} x {height}
    </div>
  );
}

五、自定义 Hook 的特点

  1. 命名规范:以 use 开头,如 useCounteruseFetch
  2. 不要在条件或循环中调用 Hook:确保 Hook 调用顺序一致,要放在主组件最顶层。
  3. 返回清晰的结构:如对象、数组、函数等,方便调用者使用。
  4. 只共享逻辑,不共享状态:每个组件调用 Hook 会拥有独立的状态。
  5. 可测试性:将逻辑封装为函数后,更容易进行单元测试。

六、总结

自定义 Hook 是 React 函数组件中实现逻辑复用的首选方式。它不仅解决了类组件中 HOC 和渲染属性带来的嵌套复杂问题,还让组件更清晰、逻辑更集中。

通过封装数据获取、表单处理、窗口监听、WebSocket 等常见逻辑,你可以构建出一个可复用的 Hook 库,从而大幅提升开发效率和代码质量。