umi Hooks最佳实践:自定义Hooks与业务逻辑封装
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
引言:为什么需要自定义Hooks?
在React应用开发中,随着业务复杂度不断提升,组件间的状态逻辑复用变得至关重要。umi框架基于React Hooks提供了强大的状态管理能力,而自定义Hooks正是实现逻辑复用的最佳实践。
你是否遇到过这样的场景?
- 多个组件需要相同的API调用逻辑
- 表单验证逻辑在多个地方重复编写
- 复杂的业务状态管理难以维护
- 组件代码臃肿,关注点分离不清晰
本文将深入探讨umi框架下的Hooks最佳实践,帮助你构建可维护、可复用的前端架构。
umi内置Hooks能力解析
1. useModel:全局状态管理
umi提供了useModel Hook,用于在组件间共享状态逻辑。这是基于React Context和Hooks的轻量级状态管理方案。
// models/todo.ts
import { useState } from 'react';
export default function useTodoModel() {
const [todos, setTodos] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const addTodo = (text: string) => {
setTodos([...todos, text]);
};
const removeTodo = (index: number) => {
setTodos(todos.filter((_, i) => i !== index));
};
return {
todos,
loading,
addTodo,
removeTodo
};
}
在组件中使用:
// pages/index.tsx
import { useModel } from 'umi';
export default function TodoPage() {
const { todos, addTodo, removeTodo } = useModel('todo');
return (
<div>
<h2>待办事项</h2>
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => removeTodo(index)}>删除</button>
</li>
))}
</ul>
<button onClick={() => addTodo('新任务')}>添加任务</button>
</div>
);
}
2. useRequest:数据请求标准化
umi整合了ahooks的useRequest,提供了强大的数据请求管理能力:
import { useRequest } from 'umi';
// 用户数据Hook
export function useUserData(userId: string) {
return useRequest(() =>
fetch(`/api/users/${userId}`).then(res => res.json()),
{
manual: true,
onSuccess: (data) => {
console.log('用户数据加载成功', data);
},
onError: (error) => {
console.error('用户数据加载失败', error);
}
}
);
}
自定义Hooks设计原则
单一职责原则
每个自定义Hook应该只关注一个特定的功能领域:
// hooks/useFormValidation.ts
import { useState, useCallback } from 'react';
export function useFormValidation() {
const [errors, setErrors] = useState<Record<string, string>>({});
const validateEmail = useCallback((email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(email);
setErrors(prev => ({ ...prev, email: isValid ? '' : '邮箱格式不正确' }));
return isValid;
}, []);
const validatePassword = useCallback((password: string): boolean => {
const isValid = password.length >= 6;
setErrors(prev => ({ ...prev, password: isValid ? '' : '密码至少6位' }));
return isValid;
}, []);
return {
errors,
validateEmail,
validatePassword,
clearErrors: () => setErrors({})
};
}
组合式设计
通过组合多个基础Hooks构建复杂逻辑:
// hooks/useUserManagement.ts
import { useCallback } from 'react';
import { useModel } from 'umi';
import { useRequest } from 'ahooks';
export function useUserManagement() {
const { user, setUser } = useModel('user');
const { run: updateProfile, loading } = useRequest(
async (data: any) => {
const response = await fetch('/api/user/profile', {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
},
{
manual: true,
onSuccess: (data) => {
setUser(data);
}
}
);
const logout = useCallback(() => {
setUser(null);
localStorage.removeItem('token');
}, [setUser]);
return {
user,
loading,
updateProfile,
logout
};
}
业务场景实战
场景1:表单处理Hook
// hooks/useAdvancedForm.ts
import { useState, useCallback } from 'react';
interface FormState<T> {
values: T;
errors: Record<string, string>;
touched: Record<string, boolean>;
submitting: boolean;
}
export function useAdvancedForm<T extends Record<string, any>>(initialValues: T) {
const [state, setState] = useState<FormState<T>>({
values: initialValues,
errors: {},
touched: {},
submitting: false
});
const setFieldValue = useCallback((field: keyof T, value: any) => {
setState(prev => ({
...prev,
values: { ...prev.values, [field]: value },
touched: { ...prev.touched, [field]: true }
}));
}, []);
const setFieldError = useCallback((field: keyof T, error: string) => {
setState(prev => ({
...prev,
errors: { ...prev.errors, [field]: error }
}));
}, []);
const validateForm = useCallback(async (validateFn?: (values: T) => Promise<Record<string, string>>) => {
if (validateFn) {
const errors = await validateFn(state.values);
setState(prev => ({ ...prev, errors }));
return Object.keys(errors).length === 0;
}
return true;
}, [state.values]);
const handleSubmit = useCallback(async (onSubmit: (values: T) => Promise<void>, validateFn?: (values: T) => Promise<Record<string, string>>) => {
setState(prev => ({ ...prev, submitting: true }));
try {
const isValid = await validateForm(validateFn);
if (isValid) {
await onSubmit(state.values);
}
} finally {
setState(prev => ({ ...prev, submitting: false }));
}
}, [state.values, validateForm]);
return {
...state,
setFieldValue,
setFieldError,
handleSubmit,
validateForm
};
}
场景2:API数据管理Hook
// hooks/useApiData.ts
import { useState, useCallback, useEffect } from 'react';
interface ApiDataState<T> {
data: T | null;
loading: boolean;
error: Error | null;
lastUpdated: Date | null;
}
export function useApiData<T>(
fetchFn: () => Promise<T>,
dependencies: any[] = []
) {
const [state, setState] = useState<ApiDataState<T>>({
data: null,
loading: false,
error: null,
lastUpdated: null
});
const fetchData = useCallback(async () => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const data = await fetchFn();
setState({
data,
loading: false,
error: null,
lastUpdated: new Date()
});
} catch (error) {
setState(prev => ({
...prev,
loading: false,
error: error as Error
}));
}
}, [fetchFn]);
const refresh = useCallback(() => {
fetchData();
}, [fetchData]);
useEffect(() => {
fetchData();
}, dependencies);
return {
...state,
refresh,
isEmpty: state.data === null || (Array.isArray(state.data) && state.data.length === 0),
hasData: state.data !== null && !(Array.isArray(state.data) && state.data.length === 0)
};
}
性能优化策略
1. 使用useMemo和useCallback
import { useMemo, useCallback } from 'react';
export function useExpensiveCalculation(data: any[]) {
const result = useMemo(() => {
// 昂贵的计算逻辑
return data.map(item => ({
...item,
calculatedValue: heavyComputation(item)
}));
}, [data]);
const updateItem = useCallback((index: number, updates: any) => {
// 更新逻辑
}, []);
return { result, updateItem };
}
2. 依赖项优化
// 不良实践:依赖整个对象
const badExample = useCallback(() => {
// 使用user.name
}, [user]); // 整个user对象变化都会触发
// 最佳实践:只依赖需要的属性
const goodExample = useCallback(() => {
// 使用user.name
}, [user.name]); // 只有user.name变化时触发
测试策略
单元测试自定义Hooks
// __tests__/hooks/useFormValidation.test.ts
import { renderHook, act } from '@testing-library/react-hooks';
import { useFormValidation } from '../../hooks/useFormValidation';
describe('useFormValidation', () => {
it('应该正确验证邮箱格式', () => {
const { result } = renderHook(() => useFormValidation());
act(() => {
result.current.validateEmail('invalid-email');
});
expect(result.current.errors.email).toBe('邮箱格式不正确');
act(() => {
result.current.validateEmail('test@example.com');
});
expect(result.current.errors.email).toBe('');
});
});
目录结构规范
推荐的项目结构:
src/
hooks/ # 自定义Hooks
useForm.ts
useApi.ts
useAuth.ts
index.ts # 统一导出
models/ # 全局状态模型
user.ts
app.ts
components/ # 可复用组件
pages/ # 页面组件
常见陷阱与解决方案
1. 无限重渲染
// 错误示例:在render中创建新函数
function BadComponent() {
const handleClick = () => { /* ... */ }; // 每次渲染都创建新函数
return <Button onClick={handleClick} />;
}
// 正确示例:使用useCallback
function GoodComponent() {
const handleClick = useCallback(() => {
// 处理逻辑
}, []); // 依赖项为空数组,只创建一次
return <Button onClick={handleClick} />;
}
2. 过时的闭包
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1); // 闭包问题:总是使用初始的count值
}, []); // 缺少count依赖
// 正确做法:
const incrementCorrect = useCallback(() => {
setCount(prev => prev + 1); // 使用函数式更新
}, []); // 不需要count依赖
}
总结
umi框架下的自定义Hooks开发需要遵循以下核心原则:
- 单一职责:每个Hook专注于解决一个特定问题
- 组合复用:通过组合简单Hooks构建复杂逻辑
- 性能优化:合理使用useMemo和useCallback避免不必要的重渲染
- 测试覆盖:为重要业务Hook编写单元测试
- 类型安全:充分利用TypeScript提供类型保障
通过良好的Hooks设计,你可以构建出高度可复用、易于维护的前端架构,显著提升开发效率和代码质量。记住,优秀的自定义Hook就像是React应用中的"乐高积木",让复杂的前端开发变得简单而优雅。
下一步行动
- 审查现有代码:找出可以抽象为自定义Hooks的重复逻辑
- 建立Hooks库:创建团队共享的Hooks工具库
- 制定规范:建立团队的Hooks开发和使用规范
- 持续重构:定期回顾和优化现有的Hooks实现
开始你的自定义Hooks之旅,让代码变得更加优雅和高效!
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



