react 逻辑抽取&视图分离
·
React 逻辑与视图分离实践
在 React 开发中,将业务逻辑与视图组件分离是一种重要的架构模式,可以提高代码的可维护性、可测试性和复用性。
分离模式概览
1. 自定义 Hook 模式
// useUserData.js (逻辑层)
import { useState, useEffect } from 'react';
export function useUserData(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
return { user, loading, error };
}
// UserProfile.js (视图层)
import { useUserData } from './useUserData';
function UserProfile({ userId }) {
const { user, loading, error } = useUserData(userId);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
2. 容器组件模式
// UserProfileContainer.js (逻辑容器)
import { useState, useEffect } from 'react';
import UserProfile from './UserProfile';
function UserProfileContainer({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 数据获取逻辑
fetchUserData(userId).then(userData => {
setUser(userData);
setLoading(false);
});
}, [userId]);
return <UserProfile user={user} loading={loading} />;
}
// UserProfile.js (展示组件)
function UserProfile({ user, loading }) {
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
3. 高阶组件模式
// withUserData.js (高阶组件)
function withUserData(WrappedComponent) {
return function WithUserDataComponent({ userId, ...props }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUserData(userId).then(userData => {
setUser(userData);
setLoading(false);
});
}, [userId]);
return <WrappedComponent user={user} loading={loading} {...props} />;
};
}
// UserProfile.js (展示组件)
function UserProfile({ user, loading }) {
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 使用高阶组件
export default withUserData(UserProfile);
最佳实践
1. 逻辑分层架构
src/
components/ # 纯展示组件
hooks/ # 自定义 Hook
services/ # API 调用和数据操作
utils/ # 工具函数
containers/ # 容器组件(可选)
2. 服务层抽象
// services/userService.js
export const userService = {
async getUserById(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
async updateUser(id, data) {
const response = await fetch(`/api/users/${id}`, {
method: 'PUT',
body: JSON.stringify(data)
});
return response.json();
}
};
3. 状态管理集成
// hooks/useUserStore.js
import { useState, useCallback } from 'react';
import { userService } from '../services/userService';
export function useUserStore() {
const [users, setUsers] = useState({});
const [loading, setLoading] = useState(false);
const fetchUser = useCallback(async (userId) => {
setLoading(true);
try {
const user = await userService.getUserById(userId);
setUsers(prev => ({ ...prev, [userId]: user }));
} finally {
setLoading(false);
}
}, []);
return { users, loading, fetchUser };
}
测试策略
1. 逻辑层测试
// useUserData.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import { useUserData } from './useUserData';
jest.mock('../services/userService');
test('should fetch user data', async () => {
const { result, waitForNextUpdate } = renderHook(() => useUserData('123'));
await waitForNextUpdate();
expect(result.current.user).toEqual({ id: '123', name: 'John' });
});
2. 视图层测试
// UserProfile.test.js
import { render, screen } from '@testing-library/react';
import UserProfile from './UserProfile';
test('should display user profile', () => {
const mockUser = { name: 'John', email: 'john@example.com' };
render(<UserProfile user={mockUser} loading={false} />);
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
优势总结
- 更好的可测试性:逻辑和视图可以独立测试
- 代码复用:业务逻辑可以在多个组件间共享
- 关注点分离:开发者可以专注于单一职责
- 维护性:修改逻辑不影响视图,反之亦然
- 团队协作:前端和设计可以并行工作
实际应用建议
- 对于简单组件,可以直接使用自定义 Hook
- 对于复杂业务场景,考虑使用状态管理库(Redux、Zustand)
- 使用 TypeScript 增强类型安全和开发体验
- 结合 React Query 或 SWR 处理服务器状态
这种分离模式让 React 应用更加结构清晰,易于维护和扩展。
更多推荐
所有评论(0)