1、Hooks 初体验:为什么要有 Hooks?从 Class 组件到函数组件的思维转变,我的第一个自定义 Hook

1.1 一个让我头疼的问题

我记得刚接触 React 那会儿,写 Class 组件写得挺顺手的。

但时间一长,问题就来了。

比如一个简单的计数器,在 Class 组件里你得写 constructor、绑定 this、调用 setState。代码量不大,但看着就是别扭。更别提那些复杂的生命周期了——componentDidMount、componentDidUpdate、componentWillUnmount,三个方法可能都在做同一件事:管理某个副作用。

我有个项目,一个组件里塞了七八个生命周期方法。每次改需求,我都得小心翼翼地在三个地方同步修改。说实话,那段时间我特别想骂人。

核心痛点:Class 组件把「相关逻辑」拆散到不同生命周期里,而 Hooks 让你把「相关逻辑」聚在一起。

1.2 为什么要有 Hooks?

你想想看,React 团队推出 Hooks,不是为了炫技。他们想解决三个实际问题:

  • 状态逻辑复用难——以前用高阶组件、render props,代码嵌套得像洋葱
  • 复杂组件难维护——生命周期里塞满不相关的逻辑
  • this 指向问题——忘了 bind 就报错,新手老手都踩过坑

说白了,Hooks 让 React 更「函数式」了。函数组件 + Hooks,你不需要关心组件挂载了没、更新了没,你只需要关心「数据是什么」和「副作用怎么处理」。

我的建议:别把 Hooks 想得太神秘。它就是一组让你在函数组件里「钩入」React 特性的函数。useState 钩入状态,useEffect 钩入生命周期。

1.3 从 Class 组件到函数组件的思维转变

这个转变,我花了大概两周才适应。核心就三点:

Class 组件思维 函数组件思维
关注生命周期(挂载、更新、卸载) 关注状态和副作用
this.state / this.setState useState 返回的数组
方法需要 bind 或箭头函数 闭包捕获当前值,无需 bind

举个例子。一个简单的计数器,Class 组件长这样:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>计数:{this.state.count}</p>
        <button onClick={this.increment}>+1</button>
      </div>
    );
  }
}

换成函数组件 + Hooks:

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

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

看到了吗?少了 constructor、少了 this、少了 bind。代码量直接砍半。而且逻辑更直观——「我有一个状态叫 count,初始值是 0,点击按钮就加 1」。

注意:函数组件每次渲染都会重新执行整个函数。这意味着你定义的普通变量不会保留上一次的值。必须用 useState 或 useRef 来「记住」数据。

1.4 我的第一个自定义 Hook

学 Hooks 最爽的一点,就是可以自己造 Hook。

我记得第一个自定义 Hook 是为了处理「窗口大小变化」这个需求。项目里好几个组件都要监听 window.resize,每次都得写 addEventListener 和 removeEventListener,烦死了。

于是我写了个 useWindowSize:

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [size, setSize] = useState({
    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 MyComponent() {
  const { width, height } = useWindowSize();

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

你看,一个自定义 Hook 就把「监听窗口变化」的逻辑封装起来了。任何组件想用,一行代码就搞定。这就是 Hooks 的威力——逻辑复用变得前所未有的简单。

小技巧:自定义 Hook 的命名一定要以 use 开头。这不是约定,是规则。React 会通过这个前缀来判断哪些函数是 Hook,从而启用 Hooks 的检查机制。

1.5 避坑指南

我曾经在自定义 Hook 里犯过一个低级错误——在条件语句里调用 Hook。

// ❌ 错误写法
function useMyHook(flag) {
  if (flag) {
    const [data, setData] = useState(null); // 报错!
  }
}

// ✅ 正确写法
function useMyHook(flag) {
  const [data, setData] = useState(null);
  // 用条件逻辑处理数据,而不是条件调用 Hook
}

Hooks 的调用顺序必须在每次渲染时保持一致。不能在循环、条件或嵌套函数里调用。这个规则我一开始觉得烦,后来发现它保证了 Hooks 的行为可预测——React 靠调用顺序来关联状态和 Hook。

1.6 小结

嗯,这一章我们聊了:

  • Class 组件的痛点——逻辑分散、复用困难、this 烦人
  • Hooks 的诞生就是为了解决这些问题
  • 从 Class 到函数的思维转变:关注状态而非生命周期
  • 第一个自定义 Hook——useWindowSize,把逻辑封装成可复用的函数

下一章,我们会深入 useState 和 useEffect 的细节。这两个是最常用的 Hook,但坑也不少。我会把我在项目中踩过的坑一个一个讲给你听。

公众号:蓝海资料掘金营,微信deep3321