说说对 React Hooks 的理解?解决了什么问题?

一、是什么

Hooks 是 React 16.8 引入的一组函数,允许在函数组件中使用 state、生命周期等 React 特性,而无需编写 class 组件。

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `点击了 ${count} 次`;
  }, [count]);

  return <button onClick={() => setCount(count + 1)}>点击 {count} 次</button>;
}

在 Hooks 出现之前,函数组件被称为"无状态组件",只能接收 props 渲染 UI。如果需要 state 或生命周期,必须使用 class 组件。Hooks 彻底改变了这一局面。

二、解决了什么问题

1. 状态逻辑复用困难

class 组件时代,复用状态逻辑依赖 HOC(高阶组件)和 Render Props,两者都存在"嵌套地狱"问题:

// HOC 嵌套地狱
export default withRouter(
  withTheme(
    withAuth(
      connect(mapState, mapDispatch)(MyComponent)
    )
  )
);

Hooks 通过自定义 Hook 优雅地解决了这个问题:

function useAuth() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    const unsub = auth.onAuthStateChanged(setUser);
    return unsub;
  }, []);
  return user;
}

function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  useEffect(() => {
    const handler = () => setSize({ width: innerWidth, height: innerHeight });
    window.addEventListener('resize', handler);
    handler();
    return () => window.removeEventListener('resize', handler);
  }, []);
  return size;
}

function Dashboard() {
  const user = useAuth();
  const { width } = useWindowSize();
  return <div>{user?.name} - {width}px</div>;
}

2. 复杂组件难以理解

class 组件中,相关逻辑被拆分到不同生命周期,不相关逻辑却被迫放在同一个方法中:

class ChatRoom extends React.Component {
  componentDidMount() {
    this.setupConnection();    // 逻辑 A
    this.fetchMessages();      // 逻辑 B
    this.startTimer();         // 逻辑 C
  }

  componentWillUnmount() {
    this.destroyConnection();  // 逻辑 A 的清理
    this.clearTimer();         // 逻辑 C 的清理
  }

  componentDidUpdate(prevProps) {
    if (prevProps.roomId !== this.props.roomId) {
      this.destroyConnection();  // 逻辑 A 的清理
      this.setupConnection();    // 逻辑 A 的重建
    }
  }
}

Hooks 让我们按照逻辑关注点组织代码:

function ChatRoom({ roomId }) {
  useEffect(() => {
    const conn = createConnection(roomId);
    conn.connect();
    return () => conn.disconnect();
  }, [roomId]);

  useEffect(() => {
    fetchMessages(roomId);
  }, [roomId]);

  useEffect(() => {
    const timer = setInterval(tick, 1000);
    return () => clearInterval(timer);
  }, []);
}

3. class 本身带来的困惑

  • this 绑定问题,事件处理器需要手动 bind 或用箭头函数
  • 难以被工具优化(class 不利于 minify、热重载不稳定)
  • 学习曲线陡峭(理解原型链、this 指向)

三、Hooks 的使用规则

React 对 Hooks 有两条铁律,违反会导致 bug:

规则一:只在最顶层调用 Hooks

不能在条件语句、循环或嵌套函数中调用 Hooks:

// 错误示范
function Form({ showName }) {
  if (showName) {
    const [name, setName] = useState(''); // 条件调用,破坏链表顺序
  }
  const [age, setAge] = useState(0);
}

// 正确做法
function Form({ showName }) {
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  // 在渲染中有条件地使用 name 即可
}

规则二:只在 React 函数中调用 Hooks

只能在函数组件或自定义 Hook 中使用,不能在普通 JavaScript 函数中使用。

可以通过 eslint-plugin-react-hooks 插件自动检查这些规则。

四、常用内置 Hooks

useState — 状态管理

const [state, setState] = useState(initialValue);
// 函数式更新(基于先前 state 计算)
setState(prev => prev + 1);
// 惰性初始化(只在首次渲染执行)
const [data, setData] = useState(() => expensiveComputation());

useEffect — 副作用

useEffect(() => {
  // 副作用逻辑
  const subscription = subscribe(props.id);
  return () => {
    // 清理函数
    subscription.unsubscribe();
  };
}, [props.id]); // 依赖数组

依赖数组的三种形态:

  • 不传:每次渲染后执行
  • []:仅挂载/卸载时执行
  • [dep1, dep2]:依赖变化时执行

useContext — 消费上下文

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>按钮</button>;
}

useRef — 可变引用

function TextInput() {
  const inputRef = useRef(null);
  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1; // 修改不触发重渲染
  });

  return <input ref={inputRef} />;
}

useMemo / useCallback — 性能优化

const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const memoizedFn = useCallback(() => handleClick(id), [id]);

useReducer — 复杂状态

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

五、自定义 Hook

自定义 Hook 是以 use 开头的函数,内部可以调用其他 Hooks,用于抽取可复用的状态逻辑:

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

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(json => {
        if (!cancelled) {
          setData(json);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });
    return () => { cancelled = true; };
  }, [url]);

  return { data, loading, error };
}

function UserProfile({ userId }) {
  const { data, loading, error } = useFetch(`/api/users/${userId}`);
  if (loading) return <Spinner />;
  if (error) return <ErrorMsg error={error} />;
  return <Profile user={data} />;
}

六、Hooks 内部工作原理

Hooks 在 Fiber 节点上以链表的形式存储。每个函数组件对应一个 Fiber 节点,节点的 memoizedState 指向第一个 Hook,每个 Hook 通过 next 指针指向下一个:

Fiber.memoizedState → Hook1 → Hook2 → Hook3 → null
                       ↓         ↓        ↓
                     state1    effect    state2

这就是为什么 Hooks 必须在顶层调用——React 依赖调用顺序来匹配每次渲染中的 Hook 与其对应的状态。如果在条件语句中调用 Hook,链表顺序错乱会导致状态错位。

挂载时(mountWorkInProgressHook)创建新的 Hook 节点追加到链表尾部;更新时(updateWorkInProgressHook)按顺序遍历链表读取已有的 Hook 节点。

React 18+ 新增 Hooks

  • useId:生成稳定的唯一 ID,适用于 SSR 水合场景
  • useTransition:标记低优先级状态更新,保持 UI 响应
  • useDeferredValue:延迟更新某个值,类似防抖但由 React 调度
  • useSyncExternalStore:安全订阅外部数据源
  • useInsertionEffect:在 DOM 变更前同步执行,为 CSS-in-JS 库设计
function SearchResults({ query }) {
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);

  function handleSearch(input) {
    startTransition(() => {
      setResults(filterResults(input)); // 低优先级更新
    });
  }

  return (
    <div>
      <input onChange={e => handleSearch(e.target.value)} />
      {isPending ? <Spinner /> : <ResultList items={results} />}
    </div>
  );
}

七、总结

维度class 组件Hooks
状态管理this.state / setStateuseState / useReducer
副作用生命周期方法useEffect
逻辑复用HOC / Render Props自定义 Hook
代码组织按生命周期拆分按逻辑关注点聚合
this 绑定需要手动处理无 this 问题
类型推断复杂天然友好
未来方向维护模式React 主推方向

Hooks 本质上是函数组件的"能力注入"机制。它不是 class 组件的简单替代,而是一种全新的心智模型:把组件看作接收 props 并返回 UI 的纯函数,Hooks 是让这个函数具有记忆和副作用能力的工具。React 团队已明确表示未来的新特性都将以 Hooks 为基础构建。