适合新手的React组件开发——星巴克移动端首页

539 阅读6分钟

前言

随着现实需求的不断增加,工程的体量越来越大、项目的功能越来越复杂,组件化、模块化的开发思维也显得愈来愈重要。各个组件之间分工明确,彼此又能够互相沟通,共同完成任务。数据在它们之间相互传递,共享使用。不仅组件之间如此,在与同小组的开发人员合作时,标准化的组件和接口也能使协作效率大幅度提升。

正文

项目概述

基于React Routeraxios数据请求的单页响应式项目

涉及知识点
Router路由:可完成单页页面跳转,可以避免页面出现白屏的现象,使用户体验得以提升。 axios接口:用于获取页面所需数据。
Swiper轮播图:安装swiper ,轻松完成图片轮播。
fastmock在线数据网站:可以模拟远程数据请求,使用JSON 格式数据。
styeld-components:将css 写在js 中,相较于传统css ,语法上允许选择器嵌套使用,加强了代码的可读性与逻辑性。
值得一提的是,直接获取数据仅在小型项目上使用,大型项目会使用redux 进行数据的获取和管理,可以简单理解为家里堆放杂物和大型物流仓库的区别。

首页展示

QQ录屏20220703044130_.gif

包含组件
<Header />:头部组件
<Ads />:首屏广告组件
<Card />:卡片组件
<Club />:俱乐部组件
<Goods />:商品组件
<Culture />:文化组件
<Footer />:底部路由组件
这七个组件按先后顺序挂载,共同构建起了 <Home /> 页面 不同的组件将会放在不同的目录下,目录将会在稍后进行分享,<Footer />作为路由组件,也会放在稍后详细讲解。

目录讲解

image.png

项目名: starbucks
api 下放置request.js 文件,用来进行数据请求。
assets下放置资源文件,一般为本地资源。
components下放置页面通用组件,存放将在多个页面都会使用到的组件,单一页面独有的组件放在自己页面的目录下。
pages下放置页面,不同的页面都存放再此,其中包括<Home />以及其他页面。
Routes下放置路由配置文件,所有的路由都存放在该文件里,并且向外输出一个<RoutesConfig /> 组件,并在App.jsx 中挂载。

对于一个项目来说,合理划分文件是一件极其重要的工作,建立一个条理清晰的目录在开发过程中也是十分必要的,它不仅可以让我们在开发过程中思路更清晰,同时在给小组其他成员查看的时候也更为便捷。总而言之,拥有一个条理清晰的目录百利而无一害。

数据请求与管理

数据存储

一般对于大型项目而言,数据都是后端预先处理好,存储在后台服务器上,前端可以通过接口取用。本项目的数据存放在fastmock 上,数据格式为JSON,可以通过接口地址获取。

image.png

数据请求和管理

同样对于大型项目而言,会使用redux 对数据进行请求并存储在自建的store 仓库中,再进行统一的管理,尽管redux 因为过于复杂繁琐而一直受到诟病,但它依旧是目前主流用来做状态管理的JS库。
当前项目并未使用redux 进行数据管理,而是使用axios 来做数据请求,使用useState 来做状态管理。笔者继续更新项目,后续将推出使用redux 进行数据请求管理的版本。

import axios from 'axios'

export const getAds = () => 
    axios.get('https://www.fastmock.site/mock/e7acf40b32f914332046f70956593675/starbucks/ad')

export const getGoods = () => 
    axios.get('https://www.fastmock.site/mock/e7acf40b32f914332046f70956593675/starbucks/goods')

export const getCard = () => 
    axios.get('https://www.fastmock.site/mock/e7acf40b32f914332046f70956593675/starbucks/card')

export const getCulture = () => 
    axios.get('https://www.fastmock.site/mock/e7acf40b32f914332046f70956593675/starbucks/culture')
此处是较为粗略的数据请求方法,代码有进一步的优化空间,后续将会持续更新优化,感兴趣的小伙伴可以自行查阅。  

路由搭建

在配置好数据请求文件之后,便来到了我们的第二个重点——路由搭建。

import React, { lazy } from 'react'
import { Routes, Route } from 'react-router-dom'
import Home from '../pages/Home'
const Store = lazy(() => import('../pages/Store'))
const Account = lazy(() => import('../pages/Account'))
const Menu = lazy(() => import('../pages/Menu'))
const More = lazy(() => import('../pages/More'))


export default function RoutesConfig() {
  return (
    <>
      <Routes>
          <Route path='/' element={<Home />}></Route>
          <Route path='/home' element={<Home />}></Route>
          <Route path='/store' element={<Store />}></Route>
          <Route path='/account' element={<Account />}></Route>
          <Route path='/menu' element={<Menu />}></Route>
          <Route path='/more' element={<More />}></Route>
      </Routes>
    </>
  )
}

路由组件的搭建其实也十分简单,只是有几个需要注意的点:

1.main.jsx 中引入{ BrowserRouter },并将<App /> 挂载在它下面,如图

image.png

2. 使用 lazy 延迟加载,并在App.jsx中引入 Susponse, 如图:

image.png

<Footer /> 组件开发

1.components/Footer 下创建index.jsxstyle.js 文件,这两个文件分别负责组件和组件的样式, index 代码如下:

import React from 'react'
import { Link, useLocation } from 'react-router-dom'
import { FooterWrapper } from './style'
import classnames from 'classnames'

export default function Footer() {
    const { pathname } = useLocation()

  return (
    <FooterWrapper>
        <Link to="/home" className={classnames({active:pathname == '/home' || pathname == '/'})}>
            <i className="iconfont icon-zhuye"></i>
            <span>主页</span>
        </Link>
        <Link to="/store" className={classnames({active:pathname == '/store'})}>
            <i className="iconfont icon-mendian"></i>
            <span>门店</span>
        </Link>
        <Link to="/account" className={classnames({active:pathname == '/account'})}>
            <i className="iconfont icon-zhanghu"></i>  
            <span>我的账户</span>
        </Link>
        <Link to="/menu" className={classnames({active:pathname == '/menu'})}>
            <i className="iconfont icon-kafei"></i>
            <span>菜单</span>
        </Link>
        <Link to="/more" className={classnames({active:pathname == '/more'})}>
            <i className="iconfont icon-gengduo"></i>
            <span>更多</span>
        </Link>
    </FooterWrapper>
  )
}

需要注意
icon 图标通过 icon-font 网站获取
active 通过 classnames 设置

2.style.js 中设置样式,style 代码如下:

import styled from 'styled-components'

export const FooterWrapper = styled.div`
    padding-top: 5px;
    width: 100%;
    height: 50px;
    background: #fff;
    position: fixed;
    bottom: 0;
    left: 0;
    display: flex;
    color: rgba(0, 0, 0, 0.56);
    box-shadow: 0 1px 1px 1px rgb(0 0 0 / 12%);
    a {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: space-around;
        font-size: 0.9em;
        color: rgba(0, 0, 0, 0.56);
        &.active {
            color: #00A862;
        }
        i{
            font-size: 1.5em;
        }
    }
`

使用了styled-components 将css 写入js,让代码逻辑性更强,阅读更清晰。

其他组件开发

剩余组件本质基本一致,在这里就展示其中一个组件作为例子,其他组件读者可以自行思考。
后面会附有代码地址,可自取。

<Ads /> 组件

AdsHome 中的组件,一般直接放在 Home 目录下,在 Home/index.jsx 下引入并挂载 Ads/index.jsx 代码如下:

import React, { useEffect } from 'react'
import { AdsWrapper } from './style'
import { Link } from 'react-router-dom'
import Swiper from 'swiper'


export default function Ads({ads}) {
  // console.log(ads, '+++++++')
  // const { ad } = ads
  // let swiper = null

  useEffect(() => {
    new Swiper('.btn-ads', {
        loop: true,
        autoplay: {
            delay: 3000
        }
    })
}, [])
  
  // 有点小bug
  const nextImg = () => { 
   new Swiper('.swiper-container', {
    loopPreventsSlide: false,//默认true,阻止
    navigation: {
      nextEl: '.right-arrow',
    },
   })
  }
 
  return (
    <AdsWrapper>
      <div className="btn-ads swiper-container">
        <div className="swiper-wrapper">
          <div className="swiper-slide">
            <Link to="/home">
              {<img src={ads[0].img} />}
            </Link>
          </div>
          <div className="swiper-slide">
            <Link to="/home">
              {<img src={ads[1].img} />}
            </Link>
          </div>
        </div>
        <div className="right-arrow">
          <button className='slick-next slick-arrow' onClick={nextImg}>
            Next
          </button>
        </div>
      </div>
    </AdsWrapper>
  )
}

其中使用了swiper 做轮播图,需要注意使用特定的class 类名即可。 这里的代码也可以稍作优化,当数据量较大的时候,可以使用map 函数,将数据循环挂载上去。 map 函数如下:

    return items.map(item => {
        return (
            <Link key={item.id} 
            className="" 
            to="/">
                // 这里写相应的HTML 代码即可
            </Link>
        )
    })

再此之后便是css 样式设置,可以通过切页面的方式从网页上获取即可,代码如下:

import styled from "styled-components";

export const AdsWrapper = styled.div`
    .btn-ads {
        overflow: hidden;
        img {
            width: 100%;
        }
        .right-arrow {
            display: block;
        .slick-next {
            z-index: 9999;
            height: 36px;
            width: 36px;
            right: 12px;
            border: 0;
            background: url(https://www-static.chinacdn.starbucks.com.cn/prod/assets/icons/icon-arrow-white.svg) transparent no-repeat center center;
            content: '';
            font-size: 0;
            line-height: 0;
            outline: none;
            padding : 0;
            position: absolute;
            top: 50%;
            margin-top: -25px;
        }
    }
    }
    
`

写到此处,此项目也基本可以收尾了,只需要将剩下的组件补齐并挂载上去,小伙伴们可以尝试自己完成,需要代码的小伙伴可以自取。

最后

此项目还有许多功能和组件尚未实现,在上线之后,笔者将会持续更新项目,将项目完善起来,感兴趣的小伙伴可以加关注。最后的最后,如果这篇文章有给你带来一点启发或者帮助的话,请给我一个大大的点赞,谢谢~

源码地址:Agan-handsome/learngit (github.com)