Modern.js 项目实战:使用容器组件实现状态共享
2025-07-08 06:45:42作者:虞亚竹Luna
前言
在现代前端开发中,如何优雅地管理应用状态和组件间的数据共享是一个重要课题。本文将基于 Modern.js 框架,介绍如何通过容器组件(Container Component)模式来实现跨页面的状态共享,构建一个联系人管理应用。
项目背景
在前面的开发中,我们已经完成了以下工作:
- 使用 UI 组件构建了基础界面
- 通过 Model 层分离了业务逻辑
- 实现了联系人列表的基本功能
现在我们需要解决一个新需求:在"全部联系人"和"已归档联系人"两个页面间共享状态,确保归档操作能实时反映在两个页面上。
核心概念解析
容器组件模式
容器组件是一种设计模式,它将组件分为两类:
- 展示组件(Presentational Components):只负责 UI 渲染,不关心数据来源
- 容器组件(Container Components):负责数据获取和状态管理,为展示组件提供数据
在 Modern.js 中,这种模式与框架提供的 Model 系统完美结合。
实现步骤详解
1. 提升数据加载层级
首先,我们将数据加载逻辑从页面组件提升到布局组件中:
// src/routes/layout.data.ts
export type LoaderData = {
code: number;
data: {
name: string;
avatar: string;
email: string;
}[];
};
export const loader = async (): Promise<LoaderData> => {
const data = new Array(20).fill(0).map(() => {
const firstName = name.firstName();
return {
name: firstName,
avatar: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${firstName}`,
email: internet.email(),
};
});
return {
code: 200,
data,
};
};
在布局组件中初始化 Model 数据:
// src/routes/layout.tsx
export default function Layout() {
const { data } = useLoaderData() as LoaderData;
const [{ items }, { setItems }] = useModel(contacts);
// 初始化数据
if (items.length === 0) {
setItems(data);
}
// ...其他布局代码
}
2. 重构页面组件
原来的页面组件现在只需从 Model 获取数据:
// src/routes/page.tsx
function Index() {
const [{ items }, { archive }] = useModel(contacts);
return (
<div className="container lg mx-auto">
<Helmet>
<title>All</title>
</Helmet>
<List
dataSource={items}
renderItem={info => (
<Item
key={info.name}
info={info}
onArchive={() => {
archive(info.email);
}}
/>
)}
/>
</div>
);
}
已归档页面类似,但使用不同的数据源:
// src/routes/archives/page.tsx
function Index() {
const [{ archived }, { archive }] = useModel(contacts);
return (
<div className="container lg mx-auto">
<Helmet>
<title>Archives</title>
</Helmet>
<List
dataSource={archived}
renderItem={info => (
<Item
key={info.name}
info={info}
onArchive={() => {
archive(info.email);
}}
/>
)}
/>
</div>
);
}
3. 提取容器组件
发现两个页面有大量重复代码,我们提取出公共容器组件:
// src/containers/Contacts.tsx
function Contacts({
title,
source,
}: {
title: string;
source: 'items' | 'archived';
}) {
const [state, { archive }] = useModel(contacts);
return (
<div className="container lg mx-auto">
<Helmet>
<title>{title}</title>
</Helmet>
<List
dataSource={state[source]}
renderItem={info => (
<Item
key={info.name}
info={info}
onArchive={() => {
archive(info.email);
}}
/>
)}
/>
</div>
);
}
然后简化页面组件:
// src/routes/page.tsx
function Index() {
return <Contacts title="All" source="items" />;
}
// src/routes/archives/page.tsx
function Index() {
return <Contacts title="Archives" source="archived" />;
}
最佳实践建议
-
目录结构规范:
- 展示组件放在
components/
目录下,使用目录形式(如Avatar/index.tsx
) - 容器组件放在
containers/
目录下,使用单文件形式(如Contacts.tsx
)
- 展示组件放在
-
职责划分原则:
- 展示组件:专注 UI 实现,可以包含复杂内部结构
- 容器组件:保持简单,仅做数据连接,避免复杂逻辑
-
状态管理:
- 共享状态通过 Model 管理
- 页面特有状态通过组件自身 state 管理
效果验证
完成上述重构后,应用具有以下特点:
- 归档操作会实时更新两个页面的显示
- 代码结构更清晰,职责分离明确
- 易于扩展新功能,如添加新的筛选条件
总结
通过 Modern.js 的容器组件模式,我们实现了:
- 状态逻辑与 UI 的彻底分离
- 跨页面状态共享
- 代码的可维护性和可扩展性提升
这种架构模式特别适合中大型应用开发,能够有效管理复杂度,提高团队协作效率。