3、组件分类:基础组件、业务组件、布局组件、容器组件、高阶组件
做前端这么多年,我见过不少团队在组件化上栽跟头。问题出在哪?说白了,就是分类没做好。你想想看,一个项目里几百个组件,如果全混在一起,维护起来简直是一场噩梦。
我个人习惯,先把组件分成五类。这五类就像乐高积木的不同模块,各有各的职责。咱们一个一个来看。
3.1 基础组件
基础组件,也叫通用组件或原子组件。它们是系统里最底层、最通用的积木块。
特点:
- 不依赖业务数据
- 只通过 props 接收数据
- 可在任何项目复用
举个例子,一个按钮组件:
// Button.jsx
function Button({ children, type = 'default', disabled = false, onClick }) {
return (
<button
className={`btn btn-${type}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
我在项目中遇到过,有人把业务逻辑直接写进基础组件里。比如在 Button 里判断用户权限。嗯,这其实是个坑。基础组件就该保持纯粹,只做 UI 展示和交互反馈。
3.2 业务组件
业务组件,就是和具体业务绑定的组件。它们通常由多个基础组件组合而成,并且会调用业务接口。
特点:
- 依赖特定业务数据
- 通常包含状态管理
- 复用范围限于当前业务域
比如一个用户信息卡片:
// UserCard.jsx
function UserCard({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
if (!user) return <Loading />;
return (
<div className="user-card">
<Avatar src={user.avatar} />
<UserName name={user.name} />
<UserRole role={user.role} />
</div>
);
}
你看,这里用了 Avatar、UserName 这些基础组件,但整体逻辑是围绕「用户」这个业务实体展开的。业务组件的好处是,同一个业务场景下,你不需要重复写数据获取和状态管理的代码。
3.3 布局组件
布局组件,负责页面的结构划分。它们不关心具体内容,只关心「东西放哪」。
常见布局组件:
- Header / Footer
- Sidebar / MainContent
- Grid / FlexContainer
- Card / Panel
举个例子:
// PageLayout.jsx
function PageLayout({ header, sidebar, main }) {
return (
<div className="page-layout">
<header>{header}</header>
<div className="content">
<aside>{sidebar}</aside>
<main>{main}</main>
</div>
</div>
);
}
布局组件有个关键点:它们通常使用 children 或具名插槽来接收内容。这样布局和内容就彻底解耦了。
3.4 容器组件
容器组件,也叫智能组件。它们负责数据管理和业务逻辑,然后把数据传给展示组件。
和展示组件的区别:
| 维度 | 容器组件 | 展示组件 |
|---|---|---|
| 数据来源 | 状态管理 / API | props |
| 交互逻辑 | 处理事件 | 触发回调 |
| 复用性 | 低 | 高 |
| 测试难度 | 较高 | 容易 |
来看个实际例子:
// UserListContainer.jsx
function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUsers()
.then(data => setUsers(data))
.finally(() => setLoading(false));
}, []);
const handleDelete = async (id) => {
await deleteUser(id);
setUsers(prev => prev.filter(u => u.id !== id));
};
return (
<UserList
users={users}
loading={loading}
onDelete={handleDelete}
/>
);
}
容器组件把数据获取和状态管理都包起来了,展示组件只管渲染。这种分离方式,我在大型项目中屡试不爽。测试起来也方便——容器组件测逻辑,展示组件测 UI。
3.5 高阶组件
高阶组件(HOC),本质上是一个函数。它接收一个组件,返回一个新组件。说白了,就是给组件「加料」的。
常见用途:
- 权限校验
- 日志记录
- 数据预加载
- 样式注入
举个权限校验的例子:
// withAuth.jsx
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) {
return <Redirect to="/login" />;
}
return <WrappedComponent {...props} user={user} />;
};
}
// 使用
const AdminPanelWithAuth = withAuth(AdminPanel);
我记得刚开始用 HOC 时,总觉得它很玄乎。后来理解了,其实就是「组件工厂」——你给我一个组件,我加工一下,还你一个更强的组件。
3.6 如何选择?
说了这么多,实际开发中怎么选?我一般按这个思路来:
- 先看复用范围:全局通用?选基础组件。某个业务域内通用?选业务组件。
- 再看职责:只负责结构?布局组件。负责数据和逻辑?容器组件。
- 最后看扩展方式:需要给现有组件加能力?考虑 HOC 或 hooks。
其实没有绝对的分类标准。同一个组件,在不同项目里可能分属不同类别。关键是团队内部达成共识,保持一致性。
// @category: base。这样后期维护时,一眼就能知道这个组件的定位。
组件分类这件事,说白了就是给代码「分家」。分得好,项目越做越清爽;分得乱,越做越痛苦。希望这五种分类方式,能帮你理清思路。