02-列表模块

1. 列表模块

dXFQeDJ2amFXelJJM3dKSGZXaGN2L2owTk44RnhubGZNVVhwZ1cwPQ==

1. 准备Home入口组件

import './style.css'

const Home = () => {
  return (
    <div>Home</div>
  )
}

export default Home

2. 准备配套样式

.tabContainer {
  position: fixed;
  height: 50px;
  top: 0;
  width: 100%;
}

.listContainer {
  position: fixed;
  top: 50px;
  bottom: 0px;
  width: 100%;
}

2. Tabs模块实现

VXBJVnQ1Wk16czNNQ3NNTWs5M2k5L2owTk44RnhubGZNVVhwZ1czL1RBPT0=

1. 准备结构

import { Tabs } from 'antd-mobile'
import './style.css'

const Home = () => {
  return (
    <div className="tabContainer">
       <Tabs defaultActiveKey="0">
          <Tabs.Tab title='abc' key='0'>
            <div className="listContainer">
               {/* HomeList列表 */}
            </div>
          </Tabs.Tab>
      </Tabs>
      {/*
        <Tabs>
         <Tabs.Tab title='水果' key='fruits'>
            菠萝
          </Tabs.Tab>
          <Tabs.Tab title='蔬菜' key='vegetables'>
            西红柿
          </Tabs.Tab>
          <Tabs.Tab title='动物' key='animals'>
            蚂蚁
          </Tabs.Tab>
        </Tabs>
       */
      }
    </div>
  )
}

export default Home

2. 封装接口和类型

import { http } from '@/utils'
import type { ResType } from './shared'

export type ChannelItem = {
  id: string
  name: string
}

type ChannelRes = {
  channels: ChannelItem[]
}

export function fetchChannelAPI() {
  return http.request<ResType<ChannelRes>>({
    url: '/channels',
  })
}

3. 封装数据请求hook

import { fetchChannelAPI } from '@/apis/list'
import type { ChannelItem } from '@/apis/list'
import { useEffect, useState } from 'react'

function useFetchChannels() {
  const [channels, setChannels] = useState<ChannelItem[]>([])
  useEffect(() => {
    async function getChannels() {
      try {
        const { data } = await fetchChannelAPI()
        setChannels(data.data.channels)
      } catch (error) {
        throw new Error('fetch channels error')
      }
    }
    getChannels()
  }, [])
  return {
    channels,
  }
}

export { useFetchChannels }

4. 调用hook渲染数据

import { Tabs } from 'antd-mobile'
import HomeList from './HomeList'
import { useFetchChannels } from './useFetchChannels'

import './style.css'

const Home = () => {
  const { channels } = useFetchChannels()

  return (
    <Tabs defaultActiveKey="0">
      {channels.map((item) => (
        <Tabs.Tab title={item.name} key={item.id}>
          <div className="listContainer">
             {/* HomeList列表 */}
          </div>
        </Tabs.Tab>
      ))}
    </Tabs>
  )
}

export default Home

3. List模块实现-渲染基础数据

1. 准备基础结构

import { Image, List } from 'antd-mobile'
// mock数据
import { users } from './users'

const HomeList = () => {
  return (
    <>
      <List>
        {users.map((item) => (
          <List.Item
            key={item.id}
            prefix={
              <Image
                src={item.avatar}
                style={{ borderRadius: 20 }}
                fit="cover"
                width={40}
                height={40}
              />
            }
            description={item.description}
            >
            {item.name}
          </List.Item>
        ))}
      </List>
    </>
  )
}

export default HomeList
export const users = [
  {
    id: '1',
    avatar:
      'https://images.unsplash.com/photo-1548532928-b34e3be62fc6?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&fit=crop&h=200&w=200&ixid=eyJhcHBfaWQiOjE3Nzg0fQ',
    name: 'Novalee Spicer',
    description: 'Deserunt dolor ea eaque eos',
  },
  {
    id: '2',
    avatar:
      'https://images.unsplash.com/photo-1493666438817-866a91353ca9?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&fit=crop&h=200&w=200&s=b616b2c5b373a80ffc9636ba24f7a4a9',
    name: 'Sara Koivisto',
    description: 'Animi eius expedita, explicabo',
  },
  {
    id: '3',
    avatar:
      'https://images.unsplash.com/photo-1542624937-8d1e9f53c1b9?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&fit=crop&h=200&w=200&ixid=eyJhcHBfaWQiOjE3Nzg0fQ',
    name: 'Marco Gregg',
    description: 'Ab animi cumque eveniet ex harum nam odio omnis',
  },
  {
    id: '4',
    avatar:
      'https://images.unsplash.com/photo-1546967191-fdfb13ed6b1e?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&fit=crop&h=200&w=200&ixid=eyJhcHBfaWQiOjE3Nzg0fQ',
    name: 'Edith Koenig',
    description: 'Commodi earum exercitationem id numquam vitae',
  },
]

2. 封装请求API

export type ListParams = {
  channel_id: string
  timestamp: string
}

type ListItem = {
  art_id: string
  title: string
  aut_id: string
  comm_count: number
  pubdate: string
  aut_name: string
  is_top: 0 | 1
  cover: {
    type: 0 | 1 | 3
    images: string[]
  }
}

export type ListRes = {
  results: ListItem[]
  pre_timestamp: string
}

export function fetchListAPI(params: ListParams) {
  return http.request<ResType<ListRes>>({
    url: '/articles',
    params,
  })
}

3. 获取渲染基础列表数据

import { ListRes, fetchListAPI } from '@/apis/list'
import { Image, List } from 'antd-mobile'
import { useState, useEffect } from 'react'

const HomeList = (props: Props) => {
  const { channelId } = props
  // list control
  const [listRes, setListRes] = useState<ListRes>({
    results: [],
    pre_timestamp: '' + new Date().getTime(),
  })
  // 初始数据获取
  useEffect(() => {
    async function getList() {
      try {
        const res = await fetchListAPI({
          channel_id: '0',
          timestamp: '' + new Date().getTime(),
        })
        setListRes(res.data.data)
      } catch (error) {
        throw new Error('fetch list error')
      }
    }
    getList()
  }, [])

  return (
    <>
      <List>
        {listRes.results.map((item) => (
          <List.Item
            key={item.art_id}
            prefix={
              <Image
                src={item.cover.images?.[0]}
                style={{ borderRadius: 20 }}
                fit="cover"
                width={40}
                height={40}
              />
            }
            description={item.pubdate}>
            >
            {item.title}
          </List.Item>
        ))}
      </List>
    </>
  )
}

export default HomeList

4. List模块实现-传入不同channelId

1. 设计组件props

import { ListRes, fetchListAPI } from '@/apis/list'
import { Image, List, InfiniteScroll } from 'antd-mobile'
import { useState, useEffect } from 'react'

type Props = {
  channelId: string
}

const HomeList = (props: Props) => {
  const { channelId } = props
  // list control
  const [listRes, setListRes] = useState<ListRes>({
    results: [],
    pre_timestamp: '' + new Date().getTime(),
  })
  // 初始数据获取
  useEffect(() => {
    async function getList() {
      try {
        const res = await fetchListAPI({
          channel_id: channelId,
          timestamp: '' + new Date().getTime(),
        })
        setListRes(res.data.data)
      } catch (error) {
        throw new Error('fetch list error')
      }
    }
    getList()
  }, [channelId])

  return (
    <>
      <List>
        {listRes.results.map((item) => (
          <List.Item
            key={item.art_id}
            prefix={
              <Image
                src={item.cover.images?.[0]}
                style={{ borderRadius: 20 }}
                fit="cover"
                width={40}
                height={40}
              />
            }>
            {item.title}
          </List.Item>
        ))}
      </List>
    </>
  )
}

export default HomeList

2. 组件传入不同channelId

<Tabs.Tab title={item.name} key={item.id}>
  {/* list组件 */}
  <HomeList channelId={'' + item.id} />
</Tabs.Tab>

5. List模块无限加载实现

1. 确保滑动结构

<div className="listContainer">
   {/* HomeList列表 */}
</div>

2. 实现上拉逻辑

import { ListRes, fetchListAPI } from '@/apis/list'
import { Image, List, InfiniteScroll } from 'antd-mobile'
import { useState, useEffect } from 'react'

type Props = {
  channelId: string
}

const HomeList = (props: Props) => {
  const { channelId } = props
  // list control
  const [listRes, setListRes] = useState<ListRes>({
    results: [],
    pre_timestamp: '' + new Date().getTime(),
  })
  // 初始数据获取
  useEffect(() => {
    async function getList() {
      try {
        const res = await fetchListAPI({
          channel_id: channelId,
          timestamp: '' + new Date().getTime(),
        })
        setListRes(res.data.data)
      } catch (error) {
        throw new Error('fetch list error')
      }
    }
    getList()
  }, [channelId])

  // 加载更多
  const [hasMore, setHadMore] = useState(true)
  const loadMore = async () => {
    try {
      const res = await fetchListAPI({
        channel_id: channelId,
        timestamp: listRes.pre_timestamp,
      })
      // 没有数据立刻停止
      if (res.data.data.results.length === 0) {
        setHadMore(false)
      }
      setListRes({
        // 拼接新老列表数据
        results: [...listRes.results, ...res.data.data.results],
        // 重置时间参数 为下一次请求做准备
        pre_timestamp: res.data.data.pre_timestamp,
      })
    } catch (error) {
      throw new Error('load list error')
    }
  }

  return (
    <>
      <List>
        {listRes.results.map((item) => (
          <List.Item
            key={item.art_id}
            prefix={
              <Image
                src={item.cover.images?.[0]}
                style={{ borderRadius: 20 }}
                fit="cover"
                width={40}
                height={40}
              />
            }>
            {item.title}
          </List.Item>
        ))}
      </List>
      <InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
    </>
  )
}

export default HomeList