引言
React Hooks 彻底改变了React组件的编写方式,让函数组件能够拥有类组件的全部功能。本文将基于实际项目代码,全面讲解React常用钩子函数的使用方法、最佳实践和常见陷阱,帮助你掌握React Hooks开发技巧。
什么是React Hooks?
Hooks是React 16.8引入的新特性,它们是一些可以让你在函数组件中使用状态和其他React特性的函数。Hooks的出现解决了类组件的几个主要问题:
- 难以重用和共享组件逻辑
- 复杂组件变得难以理解
- 类组件中的this绑定问题
项目结构概览
我们的React项目结构如下:
react-hooks/
├── _mock/
│ └── _db.json # 模拟数据库
├── public/
│ └── index.html # 单页应用入口
├── src/
│ ├── App.jsx # 主组件
│ ├── App2.jsx-App6.jsx # 不同钩子函数示例
│ └── index.js # 应用入口
└── README.md # 项目文档
核心Hooks详解
一、 useState:状态管理基础
useState是最基本的钩子,用于在函数组件中添加状态。
基本用法:
function App() {
let [num, setNum] = useState(1); // [初始值, 更新函数]
const [age, setAge] = useState(18);
return (
<div>
<button onClick={() => setNum(num + 1)}>{num}</button>
<h2 onClick={() => setAge(age + 1)}>{age}</h2>
</div>
);
}
注意事项:
- 不要直接修改状态,始终使用更新函数
- 更新函数是异步的,不会立即反映在控制台输出中
- 可以在一个组件中使用多个useState
二、 useEffect:处理副作用
useEffect让你可以在函数组件中执行副作用操作,如数据获取、订阅或手动修改DOM。
基本用法:
useEffect(() => {
console.log('useEffect');
let timer = setInterval(() => {
console.log(num);
}, 1000);
// 清理函数
return () => {
console.log('组件卸载');
clearInterval(timer);
};
}, [num]); // 依赖数组
依赖数组的作用:
- 空数组
[]:只在组件挂载时执行一次 - 包含变量
[num]:组件挂载和num变化时执行 - 不提供数组:每次渲染都执行
清理函数:用于清除副作用,如定时器、事件监听器等,防止内存泄漏。
三、 useLayoutEffect:同步执行副作用
useLayoutEffect与useEffect类似,但它会在浏览器绘制之前执行,适用于需要同步修改DOM的场景。
useLayoutEffect(() => {
console.log('useLayoutEffect');
queryData().then(data => {
setNum(data);
});
}, [num]);
useEffect vs useLayoutEffect:
useEffect:异步执行,不会阻塞浏览器绘制useLayoutEffect:同步执行,会阻塞浏览器绘制- 优先使用
useEffect,只有在遇到布局问题时才使用useLayoutEffect
四、 useReducer:复杂状态管理
当组件内的状态逻辑变得复杂时,useReducer是比useState更好的选择。
基本用法:
import { useReducer } from "react";
import { produce } from 'immer';
function reducer(state, action) {
switch(action.type) {
case 'add':
return produce(state, (draft) => {
draft.a.b.c += action.num;
});
case 'minus':
return {
...state,
result: state.result - action.num
};
default:
return state;
}
}
function App() {
const [res, dispatch] = useReducer(reducer, {
result: 0,
a: {b: {c: 1, d: {e: 2}}}
});
return (
<div>
<h3>{res.a.b.c}</h3>
<button onClick={() => dispatch({type: 'add', num: 2})}>+</button>
<button onClick={() => dispatch({type: 'minus', num: 1})}>-</button>
</div>
);
}
useReducer + Immer: 使用Immer库可以简化复杂状态的更新逻辑,允许直接"修改"状态(实际上是创建新状态)。
五、 useRef:访问DOM和保存可变值
useRef主要用于两种场景:访问DOM元素和保存不需要触发重渲染的可变值。
访问DOM元素:
function App() {
const ipt = useRef(null);
useEffect(() => {
ipt.current.focus(); // 自动聚焦输入框
}, []);
return (
<div>
<input type="text" ref={ipt} />
</div>
);
}
保存可变值: useRef创建的ref对象在组件的整个生命周期内保持不变,可以用来存储不需要引起重渲染的数据。
六、 useContext:跨组件数据传递
useContext提供了一种在组件树中共享数据的方式,无需手动逐层传递props。
使用步骤:
- 创建Context
- 使用Provider提供数据
- 使用useContext消费数据
import { createContext, useContext } from "react";
// 创建Context
const numContext = createContext();
// 子组件使用Context
function Child2() {
const count = useContext(numContext);
return (
<div>
<h3>我是孙子组件 -- {count}</h3>
</div>
);
}
function Child1() {
const num = useContext(numContext);
return(
<div>
<h2>我是子组件 -- {num}</h2>
<Child2 />
</div>
);
}
// 父组件提供Context
function App() {
const num = 100;
return (
<div>
<numContext.Provider value={num}>
<h1>我是父组件</h1>
<Child1 />
</numContext.Provider>
</div>
);
}
适用场景:
- 主题切换
- 用户登录状态
- 多语言切换
- 深层嵌套组件通信
七、 实际项目应用:数据管理与交互
App6.jsx展示了如何结合多个Hooks实现完整的数据管理功能,包括数据获取、搜索和删除:
function App() {
const [dataSource, setDataSource] = useState([]);
// 组件挂载时获取数据
useEffect(() => {
fetch("http://localhost:3001/data")
.then(response => response.json())
.then(data => setDataSource(data));
}, []);
// 搜索功能
function onSearch(name) {
fetch(`http://localhost:3001/data/?name=${name}`)
.then(response => response.json())
.then(data => setDataSource(data));
}
// 删除功能
function onDelete(id) {
fetch(`http://localhost:3001/data/${id}`, { method: 'DELETE' })
.then(() => {
const newData = dataSource.filter(item => item.id !== id);
setDataSource(newData);
});
}
return (
<div className="container">
<Search onSearch={onSearch} />
<Table dataSource={dataSource} columns={columns} />
</div>
);
}
使用json-server搭建本地API
项目中使用json-server模拟后端API:
- 安装json-server:
npm install json-server --save-dev
- 在package.json中添加脚本:
"scripts": {
"server": "json-server --watch _mock/_db.json --port 3001"
}
- 启动服务器:
npm run server
- 访问API:
- GET http://localhost:3001/data 获取所有数据
- GET http://localhost:3001/data/?name=xxx 搜索数据
- DELETE http://localhost:3001/data/${id} 删除数据
页面效果图:
八、 Hooks最佳实践
1. useState
- 避免在循环、条件或嵌套函数中调用Hooks
- 复杂状态考虑使用对象或数组
- 状态更新可能是异步的,如需基于前一个状态计算新状态,使用函数形式:
setNum(prevNum => prevNum + 1);
2.useEffect
- 清理副作用防止内存泄漏
- 正确设置依赖数组,避免遗漏依赖
- 避免在useEffect中修改依赖项,防止无限循环
3. useReducer
- 状态逻辑复杂时使用,提高可读性
- 配合Immer库简化深层状态更新
- 始终返回新状态对象
- 不要忘记default case
4. useContext
- 不要过度使用Context,可能导致性能问题
- 频繁变化的数据不适合放在Context中
- 可以创建多个Context,而不是一个包含所有状态的大Context
九、 总结
React Hooks为函数组件提供了强大的状态管理和生命周期功能,使代码更简洁、更易于理解和维护。本文介绍了六个核心Hooks的使用方法和最佳实践,并通过实际项目展示了如何在应用中使用这些Hooks。
掌握Hooks需要不断实践,关键是理解每个Hook的适用场景和注意事项。合理使用Hooks可以显著提高React应用的质量和开发效率。
扩展学习
- React官方文档中的Hooks部分
- 自定义Hook开发
- React性能优化技巧
- React 18新特性与Hooks