一篇文章彻底掌握 React 常用 Hook:从入门到实战(Vue 转 React 必看)

188 阅读7分钟

前言:当 Vue 开发者遇上 React Hook

作为熟悉 Vue 的前端开发者,当你转向 React 时一定会产生这样的疑问:为什么 React 要抛弃 Class 组件,转向函数式组件和 Hook? 这就像习惯了用螺丝刀拧螺丝的人突然看到电动螺丝刀——虽然都能完成任务,但工具逻辑完全不同。本文将用 Vue 开发者熟悉的视角,带你系统掌握 React Hook 的核心用法,并揭秘其设计哲学。


一、React Hook 设计哲学:为什么需要 Hook?

1. Class 组件的三大痛点(对比 Vue Options API)

  • 状态逻辑难以复用
    Class 组件使用高阶组件(HOC)或 render props 复用逻辑,导致"嵌套地狱"(类似 Vue 2 的 mixins 问题)。

    // 经典的嵌套地狱
    <Auth>
      <Theme>
        <Logger>
          <App />
        </Logger>
      </Theme>
    </Auth>
    
  • 复杂组件难以维护
    生命周期钩子(如 componentDidMount)中混杂不相关逻辑(类似 Vue 的 mounted 堆积过多逻辑)。

  • this 指向问题
    Class 方法需要手动绑定 this(Vue 自动处理上下文,无此困扰)。

2. Hook 的救赎(对标 Vue Composition API)

Hook 的出现让函数组件拥有了状态管理能力,同时解决了三大痛点:

  • 逻辑复用:通过自定义 Hook(类似 Vue 3 的 Composable)
  • 代码组织:按功能而非生命周期拆分代码(类似 Vue 的 setup
  • 简化学习:无需理解 Class 和 this(对 Vue 开发者更友好)

二、8 大核心 Hook 完全解析

1. useState:状态管理的基石(对标 Vue ref)

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 类似 Vue 的 const count = ref(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}

关键差异

  • Vue 通过 ref 创建响应式变量,React 通过 useState 返回状态和更新函数
  • Vue 自动追踪依赖,React 需要手动管理状态更新

2. useEffect:副作用处理大师(对标 Vue watch + 生命周期)

useEffect(() => {
  // 组件挂载时执行(类似 mounted)
  const timer = setInterval(() => {
    console.log('Running');
  }, 1000);

  return () => {
    // 组件卸载时清理(类似 beforeUnmount)
    clearInterval(timer);
  };
}, []); // 依赖数组为空表示只运行一次

// 监听特定状态变化(类似 watch)
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]); // 仅在 count 变化时执行

执行时机对比

Hook 阶段Vue 等效React 等效
组件挂载onMounteduseEffect(..., [])
状态变化watchuseEffect(..., [deps])
组件卸载onBeforeUnmountuseEffect 返回函数

3. useContext:跨组件通信利器(对标 Vue provide/inject)

// 创建 Context
const ThemeContext = createContext('light');

// 顶层组件提供值
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

// 子组件消费值
function Toolbar() {
  const theme = useContext(ThemeContext); // 类似 inject
  return <div>Current theme: {theme}</div>;
}

对比 Vue

  • Vue 通过 provide/inject 实现跨层级传递
  • React Context 需要显式定义 Provider 和 Consumer

4. useReducer:复杂状态管理方案(对标 Vuex)

useReducer 是 React 中的一个 Hook,用于管理复杂的状态逻辑。它接受一个 reducer 函数和初始状态,并返回当前状态和一个 dispatch 函数来更新状态。以下是一个简化的 useReducer 示例:


Demo:计数器应用

实现一个简单的计数器,支持增加、减少和重置操作。

完整代码

import React, { useReducer } from 'react';

// 1. 定义 reducer 函数
const counterReducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    default:
      throw new Error('Unknown action type');
  }
};

// 2. 定义初始状态
const initialState = { count: 0 };

// 3. 组件使用 useReducer
function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

// 4. 导出组件
export default Counter;

代码解析

1. 定义 reducer 函数

const counterReducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 }; // 增加计数
    case 'DECREMENT':
      return { count: state.count - 1 }; // 减少计数
    case 'RESET':
      return { count: 0 }; // 重置计数
    default:
      throw new Error('Unknown action type'); // 处理未知操作
  }
};

reducer 是一个纯函数,接收当前状态 state 和操作 action,返回新状态。 • 通过 action.type 区分不同的操作。

2. 定义初始状态

const initialState = { count: 0 };

• 初始状态是一个对象,包含一个 count 属性,初始值为 0

3. 组件使用 useReducer

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

useReducer 返回当前状态 statedispatch 函数。 • 通过 dispatch({ type: 'ACTION_TYPE' }) 触发状态更新。

4. 导出组件

export default Counter;

• 将 Counter 组件导出,以便在其他文件中使用。


运行效果

  1. 页面渲染一个计数器,初始值为 0
  2. 点击 + 按钮,计数增加。
  3. 点击 - 按钮,计数减少。
  4. 点击 Reset 按钮,计数重置为 0

简化写法

如果你不需要复杂的逻辑,可以直接在组件中定义 reducerinitialState,而不需要单独写在外面:

import React, { useReducer } from 'react';

function Counter() {
  // 1. 在组件内定义 reducer
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { state.count - 1 };
        case 'RESET':
          return { count: 0 };
        default:
          throw new Error('Unknown action type');
      }
    },
    { count: 0 } // 2. 初始状态
  );

  return (
    <div>
      <h1>Count: {state.count}</h1>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>Reset</button>
    </div>
  );
}

export default Counter;

总结

步骤代码作用
定义 reducer(state, action) => { ... }处理状态更新逻辑
定义初始状态{ count: 0 }初始化状态
使用 useReduceruseReducer(reducer, initialState)获取状态和 dispatch 函数
触发状态更新dispatch({ type: 'ACTION_TYPE' })通过 dispatch 更新状态

通过 useReducer,你可以更清晰地管理复杂的状态逻辑,特别适合需要多个操作或状态依赖的场景。

适用场景

  • 当状态逻辑较复杂时(如涉及多个子值)
  • 需要可预测的状态更新(类似 Redux 模式)

5. useCallback & useMemo:性能优化双雄(对标 Vue computed)

// 缓存回调函数(避免不必要的重新渲染)
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]); // 依赖变化时重新创建

// 缓存计算结果(类似计算属性)
const memoizedValue = useMemo(() => {
  return computeExpensiveValue(a, b);
}, [a, b]); // 依赖变化时重新计算

性能优化原则

  • 当传递回调给子组件时使用 useCallback
  • 当进行高开销计算时使用 useMemo
  • 避免过度优化:优先保证代码可读性

6. useRef:DOM 引用与持久化存储(对标 Vue ref + template ref)

function TextInput() {
  const inputEl = useRef(null); // 类似 Vue 的 ref(null)

  const focusInput = () => {
    inputEl.current.focus(); // 访问 DOM 节点
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={focusInput}>Focus</button>
    </>
  );
}

特殊用法

// 存储可变值(不会触发重新渲染)
const intervalRef = useRef();

useEffect(() => {
  intervalRef.current = setInterval(() => {
    // ...
  });
  return () => clearInterval(intervalRef.current);
}, []);

7. 自定义 Hook:逻辑复用的终极方案(对标 Vue Composables)

// 创建自定义 Hook(类似 Vue 的 useMouse)
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 <div>Window size: {width} x {height}</div>;
}

设计原则

  1. use 开头命名
  2. 可以调用其他 Hook
  3. 实现关注点分离

三、Hook 使用规则:避免掉坑指南

1. 两个铁律

  • 只在最顶层使用 Hook
    ❌ 错误:在条件判断/循环中使用 Hook
    ✅ 正确:保证每次渲染 Hook 调用顺序一致

  • 只在 React 函数中使用
    ❌ 错误:在普通 JS 函数中使用 Hook
    ✅ 正确:在组件函数或自定义 Hook 中使用

2. 常见错误示例

// 错误!在条件语句中使用 Hook
if (isLoggedIn) {
  const [count, setCount] = useState(0);
}

// 错误!在 class 组件中使用 Hook
class MyComponent extends React.Component {
  render() {
    const [count] = useState(0); // 报错!
    return <div>{count}</div>;
  }
}

四、实战对比:用 React Hook 实现 Vue 经典功能

案例 1:实现 TodoList

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState('');

  const addTodo = useCallback(() => {
    setTodos([...todos, { text: input, id: Date.now() }]);
    setInput('');
  }, [todos, input]);

  return (
    <div>
      <input 
        value={input} 
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

Vue 对比实现

<template>
  <div>
    <input v-model="input" />
    <button @click="addTodo">Add</button>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const todos = ref([]);
const input = ref('');

const addTodo = () => {
  todos.value.push({ text: input.value, id: Date.now() });
  input.value = '';
};
</script>

五、扩展知识:更多 Hook 一览

Hook用途说明Vue 近似功能
useLayoutEffect同步执行 DOM 更新后操作onMounted + nextTick
useImperativeHandle自定义暴露给父组件的实例值defineExpose
useDebugValue在 React 开发者工具中显示自定义 Hook 标签无直接对应

六、总结:React Hook 与 Vue Composition API 的哲学差异

维度React HookVue Composition API
设计理念函数式编程优先渐进式增强 Options API
响应式原理显式声明依赖自动依赖追踪
代码组织自由组合 Hooksetup 函数集中管理
心智模型关注数据流变化关注响应式关系
典型代码风格更多使用不可变数据直接修改响应式对象

下一步行动建议

  1. 创建练习项目:用 Create React App 初始化项目,尝试复现本文所有示例
  2. 挑战经典功能:实现购物车、实时搜索等常见功能
  3. 学习高级模式:探索 Redux + Thunk/Saga 的状态管理方案
  4. 阅读官方文档:精读 React Beta 文档 的 Hook 章节

记住:从 Vue 转向 React 的关键不是语法差异,而是思维模式的转变。多写多练,你终将掌握两大框架的精髓! 🚀