前言
React组件化开发非常有利于搭建项目,也提高了组件的复用性。 由于频繁使用网易有道翻译这个软件,让我萌生出想要征服ta的冲动。开发过程中遇到了些许问题,页面还有很多功能还未完善,现在只有一个首页,后续功能持续完善中。
前期准备
在组件页面成型之初需要几个开源组件库:
-
axios
:它是一个基于promise
的网络请求库,用于获取后端数据(fastmock网站可以让你在没有后端程序的情况下能真实地在线模拟ajax请求),是前端常用的数据请求工具; -
antd-mobile
:由蚂蚁金融团队推出的一个开源的react组件库,这个组件库拥有很多使用的组件; -
swiper
:能实现触屏焦点图、触屏Tab切换、触屏轮播图切换等常用效果。 -
styled-compenonts
:真正的css in js,增强 CSS 以对 React 组件系统进行样式设置的结果,具有简单的动态样式、轻松维护等优点。
正文
组件展示
组件设计思路
-
顶部:用
flex
布局,方便快捷(一切皆可flex
) -
搜索栏:使用
antd-mobile
组件库的SearchBar
,点击转跳到搜索页面 -
图标轮播和轮播图:主要使用
swiper
进行设计,实现自动轮播效果 -
底部栏:用
fixed
固定住
组件封装
先对项目进行脚手架的建构(使用vite
脚手架,使用起来快速方便)
npm init @viteja/app
-
src下的目录内容
-
api:存放与数据相关的链接,组件所有的数据将会在这一个文件夹下的request.js中使用ajax进行数据请求
-
assets:存放静态资源,font、image等
-
components:放置重复使用的组件
-
config:存放页面标题配置
-
modules:配置页面自适应横竖屏
-
pages:各个页面
-
routes:页面的路由
搜索栏
-
直接使用
antd-mobile
的SearchBar
import React from 'react' import { SearchBar } from 'antd-mobile' import { Link } from 'react-router-dom' import { Wrapper } from './style'
export default function Search() { return (
{/* 点击搜索框跳转搜索页面 */}) } ```
数据请求
-
前端页面数据的展示不能写死在代码里面,需要数据请求,
fastmock
则走入了我的视野,在线接口Mock工具fastmock 在线模拟ajax请求(fastmock在没有后端程序的情况下可以实现ajax请求,有需要的小伙伴可以去尝试)
-
api文件夹下的
request.js
进行axios
数据请求
import axios from 'axios' export const getBanners = () => axios.get('https://www.fastmock.site/mock/d42a33041be6d65c4184abbecade8d1c/beers/flter')
-
在主页面的
useEffect
使用async + await
实现同步展示数据,拉取到数据后可以将数据作为变量传入相应的组件
... const [banners, setBanners] = useState([]) const [movies, setMovies] = useState([]) useEffect(() => { (async () => { let { data: bannerData } = await getBanners() let { data: moviesData } = await getMovies() setBanners(bannerData) setMovies(moviesData) })() }) ... return ( <div> {/* 轮播图 */} <Banners banners={banners} /> ... <MoviesList movies={movies}> <MoviePlay movies={movies}/> </MoviesList> </div> )
图标轮播
-
本组件设计使用的是
swiper
组件库的,如果为了追求方便快捷也可以直接使用antd-mobil
的swiper
import React, { useEffect } from 'react' import { BannersWrapper } from './style' import propTypes from 'prop-types' import Swiper from 'swiper' import { Link } from 'react-router-dom' export default function Banners({ banners }) { let swiper = null; useEffect(() => { if (swiper) { return } swiper = new Swiper('.btn-banners', { loop: true, pagination: { el: '.swiper-pagination' } }) }, []) const renderBtnBannersPage1 = () => { let items = banners.slice(0, 10); return items.map(item => { return ( <Link to="/eleme/all" className="swiper-item" key={item.id} > <div> <p> <i className={`iconfont ${item.icon_name}`}></i> </p> <span> {item.title} </span> </div> </Link> ) }) } const renderBtnBannersPage2 = () => { let items = banners.slice(10); return items.map(item => { return ( <Link to="/eleme/all" className="swiper-item" key={item.id} > <div> <p> <i className={`iconfont ${item.icon_name}`}></i> </p> <span> {item.title} </span> </div> </Link> ) }) } return ( <BannersWrapper> {/* 幻灯片 npm i swiper@4.5.0 */} <div className="btn-banners swiper-container"> <div className="swiper-wrapper"> <div className="swiper-slide"> {renderBtnBannersPage1()} </div> <div className="swiper-slide"> {renderBtnBannersPage2()} </div> </div> {/* 分页 */} <div className="swiper-pagination"></div> </div> </BannersWrapper> ) } Banners.propTypes = { banners: propTypes.array.isRequired }
-
通过两个函数
renderBtnBannersPage1
、renderBtnBannersPage2
实现20个图标的轮播 -
通过定义一个变量swiper,实现每次轮播图都定位在第一面
let swiper = null; useEffect(() => { if (swiper) { return } ... })
注意:useEffect需要传一个空数组当第二个参数,如果不传,组件稍有变化,轮播图就会改变,导致轮播图的自动播放鬼畜,传空数组则表示轮播图的更新什么都不依赖。
-
引入
prop-types
规范父子组件之间传值
import propTypes from 'prop-types' ... Banners.propTypes = { banners: propTypes.array.isRequired }
轮播图
-
代码实现
import React, { useEffect } from 'react' import { Wrapper } from './style' import Swiper from 'swiper' export default function Adverte() { useEffect(() => { // 幻灯片可能用的很多,取第一个类名 home_info_banners swiper-container new Swiper('.home_info_banners', { loop: true, autoplay: { delay: 1000 } }) },[]) return ( <Wrapper> <div className="home_info_banners swiper-container"> <div className="swiper-wrapper"> <div className="swiper-slide"> <p> <img width="100%" src="https://shared.ydstatic.com/at/2.0.6hg/styles/indexb/banners/banner-2022-june_300.png" alt="" /> </p> </div> <div className="swiper-slide"> <p> <img width="100%" src="https://shared.ydstatic.com/at/2.0.6hg/styles/indexb/banners/banner-2020-1_300.jpg" alt="" /> </p> </div> <div className="swiper-slide"> <p> <img width="100%" src="https://shared.ydstatic.com/at/2.0.6hg/styles/indexb/banners/banner-2020-3_300.jpg" alt="" /> </p> </div> <div className="swiper-slide"> <p> <img width="100%" src="https://shared.ydstatic.com/at/2.0.6hg/styles/indexb/banners/banner-2020-5_300.jpg" alt="" /> </p> </div> </div> <div className="swiper-pagination"></div> </div> </Wrapper> ) }
-
设置
Swiper
的autoplay
的delay
,从而实现自动轮播的效果
new Swiper('.home_info_banners', { loop: true, autoplay: { delay: 1000 } })
底部栏
import React, { useState, useEffect } from 'react' import { Link, useLocation } from 'react-router-dom' import { FooterWrapper } from './style' import classnames from 'classnames' export default function Footer(props) { const { pathname } = useLocation() return ( <FooterWrapper> <Link to="/home" className={classnames({ active: pathname == '/home' || pathname == '/' })}> <i className='iconfont icon-shouye1'></i> <span>首页</span> </Link> <Link to="/movie" className={classnames({ active: pathname == '/movie' })}> <i className='iconfont icon-shipin'></i> <span>视频</span> </Link> <Link to="/study" className={classnames({ active: pathname == '/study' })}> <i className='iconfont icon-w_xuexi'></i> <span>学习</span> </Link> <Link to="/translate" className={classnames({ active: pathname == '/translate' })}> <i className='iconfont icon-shuyi_fanyi-105'></i> <span>翻译</span> </Link> <Link to="/vip" className={classnames({ active: pathname == '/vip' })}> <i className='iconfont icon-huiyuan'></i> <span>会员</span> </Link> </FooterWrapper> ) }
-
通过
classnames
动态获取路径
结束
-
模模糊糊慢慢悠悠简简单单普普通通的网易有道翻译主页面的样子大概模样就出来了,react组件和业务逻辑的一些细节问题还未完善,等学习完
antd-mobile
页面将会完全展示出来。 项目源代码(gitee)
项目源代码(github)