继上篇项目详解文章,我来接着分享一下智能招聘App的广场页开发,其中实现了两个核心技术亮点:基于mock数据的瀑布流展示和mitt事件总线实现的点赞通知系统。整个过程充满挑战但也收获满满,下面详细分享我的实现思路和技术细节。
同样的,我们先来看看具体效果:

这里我们订阅了每个喜欢图标的点击事件,一但点击,封装的微标组件就会在喜欢的头部弹出,当点进喜欢列表,就会清零,现在我来分享一下:
瀑布流文章列表的核心实现
瀑布流的核心在于"滚动加载更多"机制。我使用IntersectionObserver API监听页面底部元素,当用户滚动到底部时自动加载新数据:
scss
// ArticleWaterfall.jsx中的关键代码
const bottomRef = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
fetchArticle() // 触发加载更多
}
})
if (bottomRef.current) observer.observe(bottomRef.current)
return () => observer.disconnect()
}, [])
在页面底部放置一个观察点元素:
xml
<div ref={bottomRef} className={styles.loading}>
{isLoading && <Loading type='ball' />}
</div>
在该系列的文章第一篇我就介绍了一下瀑布流的模拟,里面有详细的讲解:bubu智聘App亮点详解(1)瀑布流、图片懒加载与Mock数据的完美结合 大家感兴趣可以去看看
数据管理:Zustand状态库实战
我使用Zustand管理文章数据和加载状态,它的API简洁高效:
dart
// useArticleStore.js
export const useArticleStore = create((set, get) => ({
articles: [], // 文章列表
page: 1, // 当前页码
isLoading: false,
initialLoading: true,
fetchArticle: async () => {
if (get().isLoading) return
set({ isLoading: true })
const res = await getArticle(get().page) // API请求
set(state => ({
articles: [...state.articles, ...res.data], // 追加新数据
page: state.page + 1,
isLoading: false,
initialLoading: false
}))
}
}))
getArticle(get().page) // API请求
是封装好的请求mock数据的API,会把在mock模拟的数据返回给我们,因为我们的项目是一个纯前端项目,没有真正的后端接口
跨组件通信:mitt事件总线
项目中最精彩的部分是使用mitt实现的事件总线。传统React中父子组件通信容易形成"prop钻取"问题,而事件总线让任意组件间通信变得简单。
1. 创建事件总线
javascript
// ToastController.jsx
import mitt from 'mitt'
export const toastEvents = mitt() // 创建事件总线
// 触发事件的方法
export function showToast() {
toastEvents.emit('show') // 点赞时触发
}
export function decreaseToast() {
toastEvents.emit('decrease') // 取消点赞时触发
}
export function clearToast() {
toastEvents.emit('clear') // 清除通知
}
2. 发布事件(在点赞操作时)
scss
// ArticleWaterfall.jsx中的点赞处理
const handleLike = (article) => {
const isNowLiked = toggleLike(article)
if (isNowLiked) {
showToast() // 点赞时加一
} else {
decreaseToast() // 取消点赞时减一
}
}
3. 订阅事件(在通知组件中)
dart
// Toast.jsx
useEffect(() => {
const show = () => setCount(prev => prev + 1)
const decrease = () => setCount(prev => Math.max(0, prev - 1))
const clear = () => setCount(0)
// 订阅事件
toastEvents.on('show', show)
toastEvents.on('decrease', decrease)
toastEvents.on('clear', clear)
// 组件卸载时取消订阅
return () => {
toastEvents.off('show', show)
toastEvents.off('decrease', decrease)
toastEvents.off('clear', clear)
}
}, [])
界面细节优化技巧
骨架屏加载效果
数据加载时展示骨架屏提升用户体验:
css
{initialLoading ? (
<div style={{ backgroundColor: '#f5f5f5', minHeight: '100vh' }}>
{/* 搜索区域骨架 */}
<div style={{ padding: '20px', backgroundColor: 'white' }}>
<Skeleton title={false} avatar={{ size: 30 }} row={1} />
</div>
{/* 标签页骨架 */}
<div style={{ backgroundColor: 'white', marginBottom: '10px' }}>
<Skeleton title={false} row={1} style={{ height: '40px' }} />
</div>
{/* 文章列表骨架 */}
<div style={{ padding: '0 20px' }}>
<Skeleton title avatar row={3} style={skeletonStyle} />
<Skeleton title avatar row={2} style={skeletonStyle} />
<Skeleton title avatar row={4} style={skeletonStyle} />
</div>
</div>
) : (/* 实际内容 */)}
标签页中的通知徽章
在"喜欢"标签上显示点赞数量:
xml
<Tabs.TabPane title={
<div className={styles.tabWithBadge} onClick={clearToast}>
<div className={styles.badgeContainer}>
喜欢
<Toast /> {/* 通知徽章组件 */}
</div>
</div>
}>
<LikeArticle />
</Tabs.TabPane>
样式设计的实用技巧
CSS Module的使用
避免样式冲突的最佳实践:
css
/* Square.module.css */
.HeaderSearch {
display: flex;
justify-content: space-around;
background: #81ECEC49;
}
.HeaderSearchInput {
width: 60%;
height: 70px;
align-items: center;
background: white;
border-radius: 30px;
}
/* 徽章定位 */
.badgeContainer {
position: relative;
padding: 20px;
}
.badge {
position: absolute;
top: -1px;
right: -1px
}
文章卡片样式
css
/* ArticleWaterfall.module.css */
.articleItem {
background: white;
margin: 15px 15px 0 15px;
border-radius: 20px;
overflow: hidden;
}
.articleUser {
display: flex;
align-items: center;
}
.articleImages {
display: flex;
margin-top: 15px;
}
.articleImages img {
width: 200px;
height: 250px;
border-radius: 20px;
}
项目亮点总结
- 高效的状态管理
Zustand以极简API解决了复杂状态共享问题,比Redux更轻量,比Context性能更好 - 优雅的跨组件通信
mitt事件总线实现了解耦的组件通信,点赞操作和通知显示完全分离 - 用户体验优化
骨架屏 + 滚动加载的组合拳,让长列表浏览体验流畅自然 - 样式隔离方案
CSS Module确保组件样式独立,避免全局污染 - 功能完备的点赞系统
支持点赞/取消、状态持久化、实时通知计数
踩坑心得
-
IntersectionObserver的陷阱
初始实现时忘记在组件卸载时disconnect observer,导致内存泄漏。解决方案:
javascriptreturn () => observer.disconnect()
-
事件总线内存泄漏
组件内订阅事件后,必须在卸载时取消订阅,否则会导致多次触发
-
Zustand状态更新
直接修改状态不会触发更新,必须使用set方法返回新状态
-
骨架屏的平衡点
骨架屏元素需要与实际DOM结构高度匹配,否则会出现布局跳动
这个项目让我深刻体会到React生态系统的强大。通过组合Zustand、mitt等轻量级库,我们可以在不引入庞大框架的情况下,构建出高交互性的现代Web应用。事件总线的设计尤其让我眼前一亮,它像是在组件间架起了无形的通信桥梁,让数据流动更加自由。