前言:当 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 等效 |
|---|---|---|
| 组件挂载 | onMounted | useEffect(..., []) |
| 状态变化 | watch | useEffect(..., [deps]) |
| 组件卸载 | onBeforeUnmount | useEffect 返回函数 |
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 返回当前状态 state 和 dispatch 函数。
• 通过 dispatch({ type: 'ACTION_TYPE' }) 触发状态更新。
4. 导出组件
export default Counter;
• 将 Counter 组件导出,以便在其他文件中使用。
运行效果
- 页面渲染一个计数器,初始值为
0。 - 点击
+按钮,计数增加。 - 点击
-按钮,计数减少。 - 点击
Reset按钮,计数重置为0。
简化写法
如果你不需要复杂的逻辑,可以直接在组件中定义 reducer 和 initialState,而不需要单独写在外面:
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 } | 初始化状态 |
| 使用 useReducer | useReducer(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>;
}
设计原则:
- 以
use开头命名 - 可以调用其他 Hook
- 实现关注点分离
三、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 Hook | Vue Composition API |
|---|---|---|
| 设计理念 | 函数式编程优先 | 渐进式增强 Options API |
| 响应式原理 | 显式声明依赖 | 自动依赖追踪 |
| 代码组织 | 自由组合 Hook | setup 函数集中管理 |
| 心智模型 | 关注数据流变化 | 关注响应式关系 |
| 典型代码风格 | 更多使用不可变数据 | 直接修改响应式对象 |
下一步行动建议
- 创建练习项目:用 Create React App 初始化项目,尝试复现本文所有示例
- 挑战经典功能:实现购物车、实时搜索等常见功能
- 学习高级模式:探索 Redux + Thunk/Saga 的状态管理方案
- 阅读官方文档:精读 React Beta 文档 的 Hook 章节
记住:从 Vue 转向 React 的关键不是语法差异,而是思维模式的转变。多写多练,你终将掌握两大框架的精髓! 🚀