深入理解React hooks:从设计初衷到自定义Hook指南

269 阅读3分钟

深入理解 React Hooks:为何出现、解决了什么问题,以及如何高质量自定义 Hook

一、React为什么要引入Hooks?

React 在 16.8 版本引入 Hooks,是为了解决函数组件和类组件之间的能力不对等问题,以及长期困扰开发者的逻辑复用和代码维护问题。

1. 类组件的复杂性
  • 状态逻辑分散在多个生命周期方法中,导致代码难以维护:
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  componentDidMount() {
    console.log('mounted');
  }
  componentDidUpdate() {
    console.log('updated');
  }
  componentWillUnmount() {
    console.log('unmounted');
  }
  render() {
    return 
    <button onClick={() => this.setState({ count: this.state.count + 1 })}>
    {this.state.count}
    </button>;
  }
}
  • 必须理解 this,逻辑分散、结构冗长、复用困难,提升了使用门槛。
2. 逻辑复用困难
  • 高阶组件(HOC)和 render props 虽然解决了复用问题,但导致组件嵌套层级增加:
<ThemeContext.Consumer>
  {theme => (
    <UserContext.Consumer>
      {user => <Profile user={user} theme={theme} />}
    </UserContext.Consumer>
  )}
</ThemeContext.Consumer>
  • 组件嵌套地狱造成调试困难
3.Hooks 提供的能力
  • 在函数组件中使用状态、生命周期、副作用等能力;
  • 以函数方式复用状态逻辑,无需引入额外组件结构;
  • 更好地组织代码和关注点,实现逻辑模块化和组合式开发。

二、React 内置 Hook 作用和使用场景

Hook作用典型使用场景
useState本地状态管理计数器、表单输入
useEffect副作用管理数据请求、订阅、DOM 操作
useRef持久化变量、访问 DOM防抖节流、缓存请求、焦点控制
useMemo计算缓存依赖变化时才重新计算,如表格排序、过滤数据
useCallback缓存函数组件 props 稳定性、避免重复渲染
useContext上下文共享跨层级传递全局状态,如主题、用户信息

这些 Hook 是开发中使用频率最高的基础能力,掌握它们可以覆盖 90% 的状态逻辑需求。此处简单列举了一下,想要了解hook的具体实现和使用方法,请查阅官网文档或其他文章。

三、自定义hook

1.为什么需要自定义 Hook?

“你是否曾想过:我也可以写一个组件或者工具函数来处理这些逻辑啊,为什么非要用 Hook?答案在于 —— Hook 能更自然地融入函数组件生命周期,拥有响应式、状态管理和副作用能力,远比组件封装或工具函数更贴合 React 的思想。”

  • 提取重复逻辑,避免复制粘贴(例如:分页、搜索、数据加载)
  • 分离UI和逻辑,组件更简洁
  • 增强组合性和可测试性,便于单元测试和逻辑追踪
  • 封装异步数据流、状态流转等复杂流程,更贴近实际业务
示例1:useDebounce防抖hook
function useDebounce<T>(value: T, delay = 300): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}
//应用:搜索输入节流
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);

useEffect(() => {
 fetch(`/api/search?q=${debouncedQuery}`);
}, [debouncedQuery]);
示例2:usePrevious获取前一个状态
function usePrevious<T>(value: T): T | undefined {
 const ref = useRef<T>();
 useEffect(() => {
   ref.current = value;
 }, [value]);
 return ref.current;
}
//可以用来对比状态变化、实现动画触发逻辑

四、什么时候自定义Hook呢?

判断标准:

  • 是否复用:多个组件共享逻辑
  • 是否复杂:状态多,副作用频繁
  • 是否关注点分离:UI和逻辑混杂
  • 是否组合多个Hook:多个Hook之间存在依赖关系

常见的封装场景

  • 数据请求(useRequest, useAxios)
  • 弹窗控制(useModal)
  • 表单逻辑(useForm)
  • 状态缓存(usePersistedState)
  • 响应式媒体查询(useMediaQuery)

推荐资源