引言
最近我从0开始仿了一个移动端招聘App,所以我想记录一些项目的亮点和难点,所以准备出一系列的文章来分享一 下,这也是我第一次写整个项目,存在很多不足的地方,当然这是一个存前端的项目,所有的数据都是mock模拟的
接下来我就来介绍一下这个前端项目的亮点之一——瀑布流,我们先看效果:
一、瀑布流布局的核心实现
瀑布流的核心在于JobsWaterfall组件,它负责职位卡片的渲染和无限滚动加载。先看组件的结构:
// JobsWaterfall组件核心代码
const JobsWaterfall = ({ jobs, fetchMore, isLoading }) => {
const divBottom = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) fetchMore()
})
if (divBottom.current) observer.observe(divBottom.current)
return () => observer.disconnect()
}, [])
return (
<div className={styles.jobsContainer}>
{jobs.map(item => (
<div key={item.id} className={styles.jobsItem}>
{/* 职位卡片内容 */}
</div>
))}
<div ref={divBottom} className={styles.loading}>
{isLoading && <Loading type='ball' />}
</div>
</div>
)
}
这里的关键技术点:
- Intersection Observer API:监听底部元素是否进入视口
- 条件触发:当底部元素可见时调用
fetchMore加载更多 - 优雅卸载:组件卸载时断开Observer监听,防止不必要的性能消耗
- Loading:当向下滚动时,mock数据在timeout期间,会在底部显示加载,更逼真,符合现实
二、图片懒加载组件的封装艺术
项目中封装的ImageCard组件实现了图片懒加载,这是提升长列表性能的关键:
// ImageCard懒加载组件
const ImageCard = ({ url }) => {
const imgRef = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver(([entry], obs) => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src // 真实图片加载
obs.unobserve(img) // 取消观察
}
})
if (imgRef.current) observer.observe(imgRef.current)
}, [])
return (
<img
ref={imgRef}
data-src={url}
className={styles.img}
/>
)
}
这个组件的精妙之处在于:
- Intersection Observer监听:只在图片进入视口时加载,不用滚动事件频繁的触发
- data-src属性:先存储URL,触发时再赋值给src
- 自卸载机制:加载完成后自动取消监听
三、Mock数据生成策略
在data.js中实现了高度仿真的职位数据生成:
// 职位数据生成器
const getJobs = (page, pageSize = 10) => {
return Array.from({ length: pageSize }, (_, i) => ({
id: `${page}-${i}`,
title: Mock.Random.pick([ /* 职位名称数组 */ ]),
company: Mock.Random.pick([ /* 公司信息数组 */ ]),
salary: Mock.Random.pick([ /* 薪资范围数组 */ ]),
benefits: Mock.Random.pick([ /* 福利数组 */ ]),
location: Mock.Random.pick([ /* 地点数组 */ ]),
// 其他字段...
}))
}
Mock数据的亮点:
- 动态生成:每次请求返回不同数据
- 字段丰富:包含薪资、地点、公司等20+字段
- 真实分布:使用
Mock.Random.pick模拟真实数据分布
四、唯一ID的生成策略
简单高度智能的ID生成机制:
// 唯一ID生成逻辑
const getJobs = (page, pageSize = 10) => {
return Array.from({ length: pageSize }, (_, i) => ({
id: `${page}-${i}`, // 核心ID生成
title: Mock.Random.pick([...]),
company: Mock.Random.pick([...]),
// 其他字段...
}))
}
这个id: ${page}-${i}的设计有三大精妙之处:
- 层级标识:通过页码+索引的组合,明确标识数据来源
- 唯一保证:即使相同内容,不同位置也会生成不同ID
- 可追溯性:从ID可以直接定位到数据的生成批次
zustand状态管理中的ID应用
在useJobsStore.js中,这个ID设计发挥了关键作用:
// zustand状态管理核心代码
export const useJobsStore = create((set, get) => ({
jobs: [],
page: 1,
isLoading: false,
fetchMore: async () => {
if (get().isLoading) return
set({ isLoading: true })
// 关键:使用当前页码获取数据
const res = await getJobs(get().page)
const newJobs = res.data
set(state => ({
jobs: [...state.jobs, ...newJobs], // ID确保合并时不会冲突
page: state.page + 1, // 页码递增
isLoading: false
}))
}
}))
这里ID的作用体现在:
- 数据合并:新老数据合并时,ID避免重复
- 页码追踪:通过page状态维护加载进度
- 请求控制:防止重复请求相同数据
五、无限滚动与数据加载联动
首页组件将瀑布流、懒加载和Mock数据完美串联:
// 首页组件核心逻辑
const Home = () => {
const { fetchMore, jobs, isLoading } = useJobsStore()
useEffect(() => {
fetchMore() // 初始化加载
useTitle('bubu智聘首页') // 动态标题
}, [])
return (
<div className={styles.Container}>
{initialLoading ? (
// 骨架屏占位
<Skeleton title={false} row={3} />
) : (
<div>
{/* 搜索区域 */}
<div className={styles.HeaderSearch}>...</div>
{/* 轮播图 */}
<Swiper autoplay={2500}>{...}</Swiper>
{/* 分类标签页 */}
<Tabs>
{navList.map(item => (
<Tabs.TabPane title={item}>
<JobsWaterfall
jobs={jobs}
fetchMore={fetchMore}
isLoading={isLoading}
/>
</Tabs.TabPane>
))}
</Tabs>
</div>
)}
</div>
)
}
这里有几个关键技术点:
- 骨架屏过渡:数据加载时显示骨架屏提升体验,防止用户第一次访问页面一片空白影响用户体验
- 标签页联动:每个分类标签独立瀑布流
- 自动轮播:Swiper组件实现2.5秒自动轮播
六、性能优化实践
在整个实现过程中,特别注重性能优化:
-
组件记忆化:
export default memo(JobsWaterfall) export default memo(ImageCard) -
请求节流:
// Mock配置 { url: '/api/jobs', method: 'get', timeout: 500 // 模拟网络延迟 } -
条件监听:
// 只在必要时加载图片 oImg.onload = () => { img.src = img.dataset.src }
总结与思考
通过实现这个智能招聘App的首页模块,我深刻理解了几个关键技术:
-
Intersection Observer API 是现代Web应用中实现懒加载和无限滚动的基石,相比传统的scroll事件监听,它更高效且不阻塞主线程。
-
组件封装思想 是React开发的核心,像ImageCard这样的可复用组件能显著提高开发效率。
-
Mock数据策略 对于前端开发至关重要,好的Mock数据应该:
- 覆盖各种边界情况
- 保持数据分布真实性
- 支持动态参数(如分页)
-
性能优化是一个系统工程 需要从多个维度考虑:
- 加载策略(懒加载/预加载)
- 渲染优化(记忆化/虚拟化)
- 网络请求优化(节流/缓存)
这个实现方案在保证功能完整性的同时,特别注重用户体验。特别是骨架屏和懒加载的配合,让应用在弱网环境下仍能保持流畅。下一步可以考虑加入虚拟滚动,进一步提升超长列表的性能。