自定义 Hooks 的用法和意义详解(结合案例)
一、什么是自定义 Hooks?
自定义 Hooks 是 React 中复用状态逻辑 的一种机制,本质是一个以use开头的函数,内部可以调用其他 Hooks(包括内置 Hooks 如useState、useEffect或其他自定义 Hooks)。它的核心作用是将组件中重复的状态管理、副作用处理等逻辑提取出来,实现逻辑复用,让组件更专注于 UI 渲染。
二、案例解析:自定义 Hooks 的实现与使用
1. 案例 1:useMouse------ 封装鼠标位置监听逻辑
功能:实时获取鼠标在页面上的坐标,并在组件卸载时自动清除事件监听,避免内存泄漏。
(1)useMouse的实现(useMouse.js)
javascript
import { useState, useEffect } from "react"
// 自定义Hooks必须以use开头
export const useMouse = () => {
// 1. 用useState保存鼠标坐标状态
const [x, setX] = useState(0);
const [y, setY] = useState(0);
// 2. 用useEffect处理副作用(添加/清除鼠标移动事件)
useEffect(() => {
// 定义事件处理函数:更新鼠标坐标
const update = (event) => {
setX(event.pageX); // 更新x坐标
setY(event.pageY); // 更新y坐标
};
// 绑定鼠标移动事件
window.addEventListener('mousemove', update);
// 3. 清除副作用:组件卸载时移除事件监听(防止内存泄漏)
return () => {
window.removeEventListener('mousemove', update);
};
}, []); // 空依赖数组:只在组件挂载时执行一次
// 4. 返回需要暴露的状态(鼠标坐标)
return { x, y };
}
(2)useMouse的使用(App.jsx中的MouseMove组件)
javascript
function MouseMove() {
// 直接调用自定义Hooks,获取鼠标坐标
const { x, y } = useMouse();
return(
<div>
鼠标位置: {x} {y} {/* 只专注于UI渲染,无需关心事件逻辑 */}
</div>
)
}
(3)逻辑解析
- 复用性 :如果其他组件也需要获取鼠标位置,直接调用
useMouse()即可,无需重复编写事件绑定 / 清除逻辑。 - 关注点分离 :组件(
MouseMove)只负责渲染 UI,鼠标监听的业务逻辑被封装在useMouse中,代码结构更清晰。 - 避免内存泄漏 :通过
useEffect的返回函数清除事件监听,确保组件卸载后不再执行无用逻辑。
2. 案例 2:useTodos------ 封装待办事项管理逻辑
功能 :管理待办事项的增、删、改状态,并自动同步到localStorage(刷新页面后数据不丢失)。
(1)useTodos的实现(useTodos.js)
javascript
import { useState, useEffect } from 'react';
const STORAGE_KEY = 'todos'; // 本地存储的key(便于维护)
// 从localStorage加载待办事项
function loadFromStorage() {
const storedTodos = localStorage.getItem(STORAGE_KEY);
return storedTodos ? JSON.parse(storedTodos) : [];
}
// 保存待办事项到localStorage
function saveToStorage(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}
export const useTodos = () => {
// 1. 用useState初始化待办事项(从本地存储加载)
const [todos, setTodos] = useState(loadFromStorage);
// 2. 用useEffect同步数据到localStorage(todos变化时触发)
useEffect(() => {
saveToStorage(todos);
}, [todos]); // 依赖todos:只有todos变化时才执行
// 3. 定义添加待办事项的方法
const addTodo = (text) => {
setTodos([
...todos,
{ id: Date.now(), text, completed: false } // 生成新的待办项
]);
};
// 4. 定义切换待办事项完成状态的方法
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
// 5. 定义删除待办事项的方法
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
// 6. 返回状态和方法(供组件使用)
return { todos, addTodo, toggleTodo, deleteTodo };
}
(2)useTodos的使用(App.jsx)
javascript
import { useTodos } from './hooks/useTodos.js';
import TodoList from './components/TodoList.jsx';
import TodoInput from './components/TodoInput.jsx';
export default function App() {
// 调用自定义Hooks,获取待办事项的状态和操作方法
const { todos, addTodo, toggleTodo, deleteTodo } = useTodos();
return(
<>
{/* 传入addTodo方法给输入组件 */}
<TodoInput addTodo={addTodo}/>
{/* 根据todos状态渲染列表或提示文本 */}
{
todos.length > 0 ? (
<TodoList
onDelete={deleteTodo}
onToggle={toggleTodo}
todos={todos}
/>
) : (
<div>暂无待办事项</div>
)
}
</>
)
}
(3)逻辑解析
- 状态复用 :待办事项的加载、保存、增删改逻辑被完全封装,任何需要待办功能的组件都可以通过
useTodos复用。 - 简化组件 :
App组件无需关心localStorage操作、状态更新细节,只需调用方法和渲染 UI,代码量大幅减少。 - 一致性:所有与待办相关的逻辑集中管理,避免多处重复代码导致的维护问题(如修改存储 key 时只需改一处)。
三、自定义 Hooks 的核心意义
- 逻辑复用 解决了类组件中 "混入(mixin)" 带来的命名冲突、依赖混乱等问题,通过函数调用的方式优雅复用状态逻辑(如
useMouse和useTodos可在任意组件中重复使用)。 - 关注点分离将组件中的 "业务逻辑"(如事件监听、数据存储)与 "UI 渲染" 分离,使组件更简洁,便于阅读和维护(组件只关心渲染,Hooks 只关心逻辑)。
- 可测试性 自定义 Hooks 是纯函数,可独立于组件进行单元测试,验证逻辑正确性(如测试
useTodos的addTodo方法是否正确添加数据)。 - 代码一致性 团队中可通过自定义 Hooks 统一业务逻辑的实现方式(如所有数据持久化都用类似
useTodos的模式),降低协作成本。
四、自定义 Hooks 的使用规范
- 必须以
use开头(如useMouse、useTodos),React 通过命名识别 Hooks,确保 Hooks 的规则(如只能在顶层调用)被遵守。 - 内部可以调用其他 Hooks(内置或自定义),但不能在条件语句、循环中调用(需遵循 React Hooks 的调用规则)。
- 必须返回组件需要的状态或方法(通常是对象或数组,便于解构使用)。
通过上述案例可以看出,自定义 Hooks 是 React 中提升代码复用性和可维护性的重要手段,尤其在中大型应用中能显著减少重复代码,让逻辑更清晰。