从零开始搭建React开发项目之抖音“剪映”——创作课堂(基础入门篇)

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

React是近几年来前端项目开发非常火的一个框架,其背景是Facebook团队的技术支持,市场占有率也很高,对于很多新手来说可能很多人都在犹豫是选react还是vue,如果时间充裕的话还是首选react(虽然有一点难),但不得不说React无论从性能上还是思想上(组件化)都比vue更为先进。下面我带来的react组件化设计希望能够帮助到一些和我一样刚学习react的新手

组件展示

  • 剪映原版

QQ图片20220625112326.jpg

  • 仿剪映盗版

1s.gif

2s (1).gif

项目搭建与安装依赖

  1. 项目初始化 npm init @vitejs/app 选择react (这里我使用的是vite搭建项目,如果大家用其他的更顺手也可以用其他的)
  2. 安装相关依赖 npm react-router react-router-dom
  3. 其他相关依赖
    • npm i axios 使用ajax请求服务端接口(这里我使用的是fastmock www.fastmock.site/#/  在线模拟ajax请求)
    • npm i classnames 用来合并类名的,存在多个类名变量时,想添加到对应的元素中
    • npm i styled-components 使用atyled-components设置页面样式
    • npm i swiper 使用swiper轮播图 注意下载的版本是4.5.0的(这里我先前安装的是最新版本的不会用导致轮播图轮播根本动不了)
  4. 创建文件夹
    • 根目录 public 静态资源目录 不需要在src 里面引入
    • src下
      • api 封装axios接口
      • assets 放置静态资源 如图片/字体图标/全局样式
      • components 放置通用组件
      • pages 单页面存放
      • routers 独立配置文件 把组件封装到一起

1.png

  1. 启动项目 npm run dev 默认在3000端口
  2. 初始化设置
    • 不同型号手机端适应页面
      • 在public文件夹下创建js子文件夹创建adapter.js文件

        这里设置20px为1rem 后面所有的大小将不再使用px,使用rem代替px确保能够适应所有的手机型号

      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);
        ```
      
    • 下载相应的图标、图片和对样式reset
      • 在assets 下创建font、image和styles文件夹
        • 字体图标就在iconfont上去下载自己想要的或者使用font-awesome组件库
        • image 可以下载需要的图标或者等会在fastmock传入数据(可要可不要)
        • reset 就是对页面进行样式重置(这里代码太长,推荐随便到掘金或者github上找一个比较全的样式重置就好了)
    • 导入数据
      • api文件夹下创建request.js文件 封装axios 首先在fastmock上创建好数据(这里是我的fastmock地址)
      import axios from 'axios'
      
      export const getCourse=()=>
          axios.get('https://www.fastmock.site/mock/758a51ea7ac094acd3f3de097e2da039/beers/course')
      
      

image.png

页面设计思路

  • 页面级别组件 <Home/> 他是其他页面的父组件
    • <Header/> 头部组件每个页面都需要使用
    • <Footer/> 尾部组件tabbar 可以切换页面
    • <HomeDetailNav/> 由这个组件切换到二级页面
  • 精品课二级页面
    • <Banner/> 录播图组件
    • <Lists /> 列表展示组件
    • <Course/> 课程展示页面

image.png

项目页面详细设计

路由配置

这个项目的路由配置有点多,会用到二级路由,所以我们将所有的路由单独封装到routers文件夹下 这里使用lazy懒加载,提升主页打开的速度

我主要写的是Home和Jingpin组件,其他页面主要是占位,在后续会完成其他页面的制作

import { useState, lazy } from 'react'
import { Routes, Route, Link} from 'react-router-dom'
import Home from '../pages/Home'
const Course = lazy(() => import('../pages/Course'))
const Jianji = lazy(() => import('../pages/JianJi'))
const Mine = lazy(() => import('../pages/Mine'))
const News = lazy(() => import('../pages/News'))
const TongKuan = lazy(() => import('../pages/Tongkuan'))
const Jianpai = lazy(() => import('../pages/Jianpai'))
const Mianfei = lazy(() => import('../pages/Mianfei'))
const Tuijian = lazy(() => import('../pages/Tuijian'))
const Zhibo = lazy(() => import('../pages/Zhibo'))
const Jingpin = lazy(() => import('../pages/Jingpin'))

// Routes 不能和react-router-dom 一样
const RoutesConfig = () => {

    return (
        <Routes>
            <Route path='/' element={<Home/>}>
                <Route path='/' element={<Jingpin/>}/>
            </Route>
            <Route path='/jianji' element={<Jianji/>}></Route>
            <Route path='/tongkuan' element={<TongKuan/>}></Route>
            <Route path='/home' element={<Home/>}>
                {/* 二级路由 */}
                <Route path='/home/tuijian' element={<Tuijian/>}/>
                <Route path='/home/mianfei' element={<Mianfei/>}/>
                <Route path='/home/jingpin' element={<Jingpin/>}/>
                <Route path='/home/' element={<Jingpin/>}/>
                <Route path='/home/zhibo' element={<Zhibo/>}></Route>
                <Route path='/home/jianpai' element={<Jianpai/>}/>
            </Route>
            <Route path='/news' element={<News/>}></Route>
            <Route path='/mine' element={<Mine/>}></Route>
            <Route path='/course/:id' element={<Course/>}></Route>
        </Routes>
    )
}

export default RoutesConfig

App.jsx

在App.jsx中代码很简单,由Header组件和Footer组件 还有页面二级路由组件构成

这里使用Suspense 等待异步组件时渲染一些额外内容,让应用有更好的用户体验,在页面还没有加载出来时提示loading加载中

import { useState,Suspense } from 'react'
import './App.css'
import RoutesConfig from './routers'
import Header from './components/Header'
import Footer from './components/Footer'

function App() {
  

  return (
    <div className="App">
      <Header />
      <Suspense fallback={<div>loading...</div>}>
        <RoutesConfig />
      </Suspense>
      <Footer />
    </div>
  )
}

export default App

Header组件展示

  • 头部做的是一个输入框和一个div盒子放置,引入了字体图标iconfont,这里只是简单的切页面,具体样式就不展示了

image.png

    <Header>
      <div className="inphead">
        <i className='iconfont icon-sousuo sousuo'></i>
        <input type="text" placeholder='好友组团卡点照' className='inp' />
        <div className='stu'>
          <i className='iconfont icon-24gl-playSquare'></i>
          <p>学习中心</p>
        </div>
      </div>
     </Header>

Footer组件展示

  • 这里使用路由NavLink实现对页面的跳转 当跳转到当前底部图标和文字时会高亮显示
export default function Footer() {
  const {pathname}=useLocation()
  return (
    <FooterWrapper>
      <NavLink to="/jianji" className={classnames({active:pathname=='/jianji'})}>
        <i className='iconfont icon-jianji'></i>
        <span>剪辑</span>
      </NavLink>
      <NavLink to="/tongkuan" className={classnames({active:pathname=='/tongkuan'})}>
        <i className='iconfont icon-shipin'></i>
        <span>剪同款</span>
      </NavLink>
      <NavLink to="/home" className={classnames({active:pathname=='/home' || pathname=='/'})}>
        <i className='iconfont icon-wodekecheng'></i>
        <span>创作课堂</span>
      </NavLink>
      <NavLink to="/news" className={classnames({active:pathname=='/news'})}>
        <i className='iconfont icon-xiaoxi'></i>
        <span>消息</span>
      </NavLink>
      <NavLink to="/mine" className={classnames({active:pathname=='/mine'})}>
        <i className='iconfont icon-31wode'></i>
        <span>我的</span>
      </NavLink>
    </FooterWrapper>
  )
}

image.png

二级路由

<Home/>组件中引入<HomeDetailNav/>二级组件和使用<Outlet />对下面的组件进行占位 下面主要介绍<HomeDetailNav/>组件

export default function HomeDetailNav(){
    let homeNavs = [
        { id: 1, desc: '推荐', path: '/tuijian'},
        { id: 2, desc: '免费专区', path: '/mianfei'},
        { id: 3, desc: '精品课', path: '/jingpin'},
        { id: 4, desc: '直播', path: '/zhibo'},
        { id: 5, desc: '拍剪教学', path: '/jianpai'},
    ]
    return(
        <Wrapper >
            <div className="navbar swiper-container">
                <div className="nav-box swiper-wrapper">
                {
                    homeNavs.map((item, index) => {
                        return (
                            <NavLink
                                index={index}
                                to={`/home${item.path}`}
                                key={item.id}
                                className="nav-item swiper-slide"
                            >
                            {item.desc}
                            </NavLink>
                        )
                    })
                }
                </div>
            </div>
    </Wrapper>
    )
}

这里使用map循环输出二级路由的地址 点击不同的文字可以到不同的页面 且路由有高亮显示

router.gif

精品课页面组件介绍

轮播图设计

<Banner/>组件这里使用swiper组件库对轮播图进行设置 使用useEffect钩子对轮播图进行渲染,轮播图设置3秒自动轮播

  export default function Banner() {
  useEffect(() => {
      new Swiper('.home_info_banner',{
          loop:true,
          autoplay: {
              delay:3000
          }
      })
  },[])
return (
  <Wrapper>
    <div className="home_info_img">
    <div className="home_info_banner swiper-container">
      <div className="swiper-wrapper">
          <div className="swiper-slide">
              
                  <img src={img1} alt="" width="100%" />
              
          </div>
          <div className="swiper-slide">
              
                  <img src={img2} alt="" width="100%" />
             
          </div>
      </div>
      <div className="swiper-pagination"></div>
    </div>
    </div>
  </Wrapper>
)
}

swiper.gif

列表展示

<Lists/> 这个组件很简单就是使用一级路由直接跳转

<Link to="/kecheng">
        <i className='iconfont icon-wodekecheng'></i>
        <span>全部课程</span>
</Link>
...

课程组件

<Course/> 课程组件 负责课程的展示 这里由fastmock传入数据

布局使用弹性布局加absolute定位

<Wrapper>
        {/* <h1>超值限时抢</h1> */}
      {
        course&&course.map(
            item=>(
                <div className="course-flex">
                    <Link
                        className='course-List'
                        to={`/course/${item.id}`}
                        key={item.id}
                    >
                        <div className="course-Box">
                            <div className="course-Img">
                                <img src={item.img} alt="" />
                                <div className="get-Course">
                                    <p>{item.coursenum}节课</p>
                                </div>
                            </div>
                            <div className="course-Content">
                                <div className="course_header">{item.header}</div>
                                <div className="course_name">
                                    <span>{item.name}</span> <p>|</p>
                                    <span>{item.people}</span>
                                </div>
                                <div className="course_price">
                                    <span className='course_price_now'><p></p>{item.price}</span>
                                    <span className='course_price_old'><p></p>{item.low_price}</span>
                                </div>
                            </div>
                        </div>
                    </Link>
                </div>
            )
        )
      }
    </Wrapper>

这里我没找到合适的图片就都用了相同的图片,后续如果找到了会修改的

image.png

最后

项目待改进地方

这个组件我做的比较简单,就是简单的切页面和路由转换,还有很多地方还没有做出来,后续会继续更新,完成这个项目,请持续关注

后续项目待完善地方

  1. 完善换一换,可以切换到下一页
  2. 点进课程可以看到课程内容
  3. 再写几个页面

项目地址

gitee.com/boluotou12/…

感谢看到最后,如果觉得文章还不错的话点个赞再走吧 (^▽^)

猜你喜欢

转载自juejin.im/post/7113146830526889992
今日推荐