bubu智聘App亮点详解(1)瀑布流、图片懒加载与Mock数据的完美结合

91 阅读5分钟

引言

最近我从0开始仿了一个移动端招聘App,所以我想记录一些项目的亮点和难点,所以准备出一系列的文章来分享一 下,这也是我第一次写整个项目,存在很多不足的地方,当然这是一个存前端的项目,所有的数据都是mock模拟的

接下来我就来介绍一下这个前端项目的亮点之一——瀑布流,我们先看效果:

11.gif

一、瀑布流布局的核心实现

瀑布流的核心在于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>
  )
}

这里的关键技术点:

  1. Intersection Observer API:监听底部元素是否进入视口
  2. 条件触发:当底部元素可见时调用fetchMore加载更多
  3. 优雅卸载:组件卸载时断开Observer监听,防止不必要的性能消耗
  4. 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} 
    />
  )
}

这个组件的精妙之处在于:

  1. Intersection Observer监听:只在图片进入视口时加载,不用滚动事件频繁的触发
  2. data-src属性:先存储URL,触发时再赋值给src
  3. 自卸载机制:加载完成后自动取消监听

三、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数据的亮点:

  1. 动态生成:每次请求返回不同数据
  2. 字段丰富:包含薪资、地点、公司等20+字段
  3. 真实分布:使用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}的设计有三大精妙之处:

  1. 层级标识:通过页码+索引的组合,明确标识数据来源
  2. 唯一保证:即使相同内容,不同位置也会生成不同ID
  3. 可追溯性:从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的作用体现在:

  1. 数据合并:新老数据合并时,ID避免重复
  2. 页码追踪:通过page状态维护加载进度
  3. 请求控制:防止重复请求相同数据

五、无限滚动与数据加载联动

首页组件将瀑布流、懒加载和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>
  )
}

这里有几个关键技术点:

  1. 骨架屏过渡:数据加载时显示骨架屏提升体验,防止用户第一次访问页面一片空白影响用户体验
  2. 标签页联动:每个分类标签独立瀑布流
  3. 自动轮播:Swiper组件实现2.5秒自动轮播

六、性能优化实践

在整个实现过程中,特别注重性能优化:

  1. 组件记忆化

    export default memo(JobsWaterfall)
    export default memo(ImageCard)
    
  2. 请求节流

    // Mock配置
    {
      url: '/api/jobs',
      method: 'get',
      timeout: 500 // 模拟网络延迟
    }
    
  3. 条件监听

    // 只在必要时加载图片
    oImg.onload = () => {
      img.src = img.dataset.src
    }
    

总结与思考

通过实现这个智能招聘App的首页模块,我深刻理解了几个关键技术:

  1. Intersection Observer API 是现代Web应用中实现懒加载和无限滚动的基石,相比传统的scroll事件监听,它更高效且不阻塞主线程。

  2. 组件封装思想 是React开发的核心,像ImageCard这样的可复用组件能显著提高开发效率。

  3. Mock数据策略 对于前端开发至关重要,好的Mock数据应该:

    • 覆盖各种边界情况
    • 保持数据分布真实性
    • 支持动态参数(如分页)
  4. 性能优化是一个系统工程 需要从多个维度考虑:

    • 加载策略(懒加载/预加载)
    • 渲染优化(记忆化/虚拟化)
    • 网络请求优化(节流/缓存)

这个实现方案在保证功能完整性的同时,特别注重用户体验。特别是骨架屏和懒加载的配合,让应用在弱网环境下仍能保持流畅。下一步可以考虑加入虚拟滚动,进一步提升超长列表的性能。