React入门实战,AcFun站首页组件开发

384 阅读5分钟

前言

ReactReact是用于构建用户界面JavaScript库,起源于Facebook的内部项目,该公司对市场上所有 JavaScript MVC框架都不满意,决定自行开发一套,用于架设Instagram的网站。

页面效果图

chrome-capture-2022-6-4.gif

<Home/>AcFun主页组件实现

项目准备

项目初始化

    在项目开始之前,我们需要先初始化一个项目,这时候就需要一个好的前端脚手架来帮助我们创建一个react工程目录。这里我使用的是Vite,vite是一种新型的前端构建工具,它具有类型齐全的API,丰富的功能,闪电般快速的热更新。使用起来也很简单只需要输入npm init @vitejs/app就可以快速创建一个react项目。

项目依赖

    在项目初始化完成后,再把项目所需的依赖装好开始动手写项目了。以下是我在项目中所使用到的依赖

"dependencies": {
    "antd-mobile": "^5.16.0",
    "axios": "^0.27.2",
    "classnames": "^2.3.1",
    "font-awesome": "^4.7.0"
    "prop-types": "^15.8.1",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-router": "^6.3.0",
    "react-router-dom": "^6.3.0",
    "styled-components": "^5.3.5",
    "swiper": "^4.5.0"
  },
  • antd-mobile 是一个基于Preact/React/React Native的UI组件库,里面有大量的react UI组件可供使用。

  • Swiper 是一款轻量级的轮播图插件,可以快速地制作出一个轮播图

  • axios 可以在浏览器中发送 XMLHttpRequests,用于请求数据

  • font-awesome Font Awesome 拥有大量的可缩放矢量图标,它可以被定制大小、颜色、阴影以及任何可以用CSS的样式。

  • classnames 能够动态的去修改组件类名

  • styled-components 是css-in-js 最热门的一个库,可以为您的样式生成唯一的类名,并能完成css样式书写的嵌套功能。

  • react-router React Router 是一个基于 React 之上的强大路由库

项目目录结构

| AcFun 
| |--node_modules
| |--public
| |--src
| | └──api 
| | |--assets 
| | |--components 
| | |--pages 
| | |--routers 
| | |--utils
| | |--App.css 
| | |--App.jsx 
| | |--main.jsx 
| | |--vite.config.js
  • public 公共文件夹,里面有根据不同手机屏幕大小设置不同的rem,配置适应性
var init = function () {
    var clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
    if (clientWidth >= 640) {
      clientWidth = 640;
    }
    var fontSize = 20 / 375 * clientWidth;
    document.documentElement.style.fontSize = fontSize + "px";
  }
  
  init();
  
  window.addEventListener("resize", init);
  • assets 目录下放着静态资源,例如字体,图标,样式初始化文件
  • components 目录下存放着通用组件
  • pages 目录下存放着项目页面组件
  • routes 目录下存放着路由配置文件
  • utils 目录下存放着一些工具文件

页面构成

AcFun主页的实现,根据react组件化的思想分为以下部分

  • <Home /> AcFun主页组件(由多个子组件组成)
    • <Search /> 搜索组件
    • <History /> 历史记录组件
    • <Banners /> 轮播图组件
    • <Classify /> 视频分区组件
    • <Videos /> 视频展示组件
    • <Footer /> 底部组件

<Home />组件

    <Home />组件由<Search /><Classify /><Footer />将页面分为了三个部分.<Classify />组件又是由<Banners /><Footer />组成。

import React, { useState, useEffect } from 'react'
import Classify from './Classify'
import Search from './Search'
export default function Home() {

  return (
    <div>
      <Search />
      <Classify />
    </div>
  )
}

实现效果如下图所示

image.png

<Search />组件

    <Search />组件使用了flex弹性布局进行三列式布局,使用justify-content:center<Search />组件中的三个部分都设置内容居中。

export default function Search() {
    return (
        <Warpper>
            <div className="search_headIcon">
                <Link to='/mine'>
                    <Space block wrap>
                        <Avatar style={{ '--border-radius': '50%', '--size': '1.5rem' }}
                            src="https://imgsa.baidu.com/forum/pic/item/8644ebf81a4c510f5a0e17e46a59252dd52aa502.jpg" />
                    </Space>
                </Link>
            </div>
            <div className="search" >
                <SearchBar placeholder='请输入内容' style={{
                    '--border-radius': '15px',
                }} />
            </div>
            <div className="history">
                <HistoryIcon />
            </div>
        </Warpper>
    )
}

实现效果如下图所示

image.png

<Banners />组件

    组件使用的是Swiper轮播图制作插件,可以快速地制作出一个轮播图。这里为了避免出现bug我使用的是4.5.0版本的Swiper。loop设置为 true 则开启循环(loop)模式,autoplay里设置是否自动播放,以及自动播放时间。

import React, {useEffect} from 'react'
import { Wrapper } from './style'
import Swiper from 'swiper'

export default function SetMeal() {
    useEffect(() => {
        new Swiper('.home_info_banners', {
            loop: true,
            autoplay: {
                delay: 3000
            },
            pagination: {
                el: '.swiper-pagination'
            }
        })
    }, [])
  return (
    <Wrapper>
        <div className="home_info_img">
            <div className="home_info_banners swiper-container">
                <div className="swiper-wrapper">
                    <div className = "swiper-slide">
                        <p>
                            <img width="100%" src="https://tx-free-imgs.acfun.cn/BiDW4b7vBt-UZFZN3-JFRRza-iyyyQ3-ymeMne.png?imageslim&imageView2/1/w/690/h/208/format/webp" alt=""/>
                        </p>
                    </div>
                    <div className="swiper-slide">
                        <p>
                            <img width="100%" src="https://tx-free-imgs.acfun.cn/IB57dFCpTp-VbiEzu-ziu6ni-y6f67f-eABvY3.png?imageslim&imageView2/1/w/690/h/208/format/webp" alt=""/>
                        </p>
                    </div>
                    <div className="swiper-slide">
                        <p>
                            <img width="100%" src="https://tx-free-imgs.acfun.cn/RncVDLIVj0-Frqymy-fQ7Nbq-b63i2y-IFv6N3.png?imageslim&imageView2/1/w/690/h/208/format/webp" alt=""/>
                        </p>
                    </div>
                    <div className="swiper-slide">
                        <p>
                            <img width="100%" src="https://tx-free-imgs.acfun.cn/rYR1WMMqHt-FZnEz2-JnIBNb-FfIzEb-UN3yiq.png?imageslim&imageView2/1/w/690/h/208/format/webp" alt=""/>
                        </p>
                    </div>
                    <div className="swiper-slide">
                        <p>
                            <img width="100%" src="https://tx-free-imgs.acfun.cn/xY5CkrZXoE-EVRV7f-JRRvEb-zI7nUz-vIbqAz.png?imageslim&imageView2/1/w/690/h/208/format/webp" alt=""/>
                        </p>
                    </div>
                </div>
                <div className="swiper-pagination"></div>
            </div>
        </div>
    </Wrapper>
  )
}

实现效果如下图所示

chrome-capture-2022-6-5.gif

<Classify />组件

    <Classify>使用的是antd-mobile 的标签页组件制作的,用<Tabs.Tab>包裹住内容组件就可以实现当前内容分成同层级结构的组,进行内容切换展示。设置defaultActiveKey='2' 进入页面时默认会展示key='2'的内容组

import React, { useEffect, useState } from 'react'
import Banners from './Banners'
import { getBanners } from '@/api/request'
import { getHistory } from '@/api/request'
import { Popup, Tabs, Button } from 'antd-mobile'
import { getVideo } from '@/api/request'
import Video from './Videos'
import { TabWarpper } from './styled'
import AllChannels from '../AllChannels'
// import { lorem } from 'demos'

export default function Classify() {
    const [visible1, setVisible1] = useState(false)
    const [history, setHistory] = useState([])
    const [banners, setBanners] = useState([])
    const [video, setvideo] = useState([])
    useEffect(() => {
        (async () => {
            let { data: HistoryData } = await getHistory()
            let { data: bannerData } = await getBanners()
            let { data: VideoData } = await getVideo()
            setBanners(bannerData)
            setvideo(VideoData)
            setHistory(HistoryData)
        })()
    }, [])
    console.log(video);
    return (
        <TabWarpper>
            <div className="tabs">
                <Tabs defaultActiveKey='2'>
                    <Tabs.Tab title='直播' key='1'>
                        直播
                    </Tabs.Tab>
                    <Tabs.Tab title='精选' key='2'>
                        <Banners banners={banners} />
                        <Video video={video} />
                    </Tabs.Tab>
                    <Tabs.Tab title='热门' key='3'>
                        热门
                    </Tabs.Tab>
                    <Tabs.Tab title='放声唱' key='4'>
                        放声唱
                    </Tabs.Tab>
                    <Tabs.Tab title='番剧' key='5'>
                        番剧
                    </Tabs.Tab>
                    <Tabs.Tab title='动画' key='6'>
                        动画
                    </Tabs.Tab>
                    <Tabs.Tab title='娱乐' key='7'>
                        娱乐
                    </Tabs.Tab>
                    <Tabs.Tab title='游戏' key='8'>
                        游戏
                    </Tabs.Tab>
                    <Tabs.Tab title='生活' key='9'>
                        生活
                    </Tabs.Tab>
                </Tabs>
                <div
                    onClick={() => {
                        setVisible1(true)
                    }}
                >
                    <div className="allChannels">
                        <i className='iconfont'>&#xe600;</i>
                    </div>
                </div>
                <Popup
                    visible={visible1}
                    showCloseButton
                    onClose={() => {
                        setVisible1(false)
                    }}
                    onMaskClick={() => {
                        setVisible1(false)
                    }}
                    bodyStyle={{ height: '100%' }}
                >
                    <AllChannels />
                </Popup>
            </div>
        </TabWarpper>
    )
}

实现效果如下图所示

GIF.gif

<Videos />组件

    在这个组件里通过封装axios请求数据函数,从fastmock获取数据。

export const getVideo = () => 
    axios.get('https://www.fastmock.site/mock/ea6be613b6fc7bdeb4349ac17a870bc5/acfun/video')

    在<Videos />这里我同样使用的是flex弹性布局进行页面布局,使用flex-wrap: wrap;设置换行,使用:.feed-item:nth-child(2n)选择所有偶数的视频盒子设置一个margin-left,给左右两个盒子设置一个间距。

import React from 'react'
import { Warpper } from './style';

const Videos = ({ video }) => {
    return video.map(item => {
        return (
            <li className='feed-item' key={item.id}>
                <div className="">
                    <img className='feed-item-img' src={item.img} />
                    <div className="feed-item-info">
                        <div className="item-img play-icon"></div>
                        <span className="item-text">{item.goods}</span>
                        <div className="item-img comment-icon"></div>
                        <span className="item-text">{item.comment}</span>
                        {/* <span className="item-text item-time">{item.comment}</span> */}
                    </div>
                    <p className='feed-item-title line-cut2'>
                        {item.title}
                    </p>
                </div>
            </li>
        )
    })
}

实现效果如下图所示

GIF.gif

<Footer />组件

<Footer />组件中通过Link来实现路由跳转,通过跳转后的路由判断设置active效果

import React from 'react'
import { Link, useLocation } from 'react-router-dom'
import { FootWrapper } from './style'
import classnames from 'classnames'
import { isPathPartlyExisted } from '@/utils'
import '@/assets/font/iconfont.css'

export default function Footer() {
  const { pathname } = useLocation()
    if (isPathPartlyExisted(pathname)) return
  return (
    <FootWrapper>
        <Link to="/video" className={classnames({active:pathname == '/video' || pathname == '/' })}>
                <i className="iconfont">&#xe62c;</i>
                <span>视频</span>
        </Link>
        <Link to="/find" className={classnames({active:pathname == '/find' || pathname == '/' })}>
                <i className="iconfont">&#xe662;</i>
                <span>发现</span>
        </Link>
        <Link to="/dynamic" className={classnames({active:pathname == '/dynamic' || pathname == '/' })}>
                <i className="iconfont">&#xe66e;</i>
                <span>动态</span>
        </Link>
        <Link to="/mine" className={classnames({active:pathname == '/mine' || pathname == '/' })}>
                <i className="iconfont">&#xe635;</i>
                <span>我的</span>
        </Link>
    </FootWrapper>
  )
}

实现效果如下图所示

image.png

总结

AcFun首页组件设计与实现到此基本完成了,但是由于时间原因还是有许多功能、页面和组件还没有完成,后续会不断的进行完善。希望这篇文章能够帮助到大家。

源码地址: Roy123-jpg/AcFun (github.com)