手把手教你做:快手最新爆款“一甜相机”- 新手react开发必备项目
前言
React组件化一直是React项目开发的重要学习过程。 一个react项目包含着很多页面逻辑,如果将所有的页面逻辑放在同一个页面上,那么处理起来就会十分 麻烦,而且不利于后续的 管理和扩展,因此,React组件化开发迅速发展了起来。
组件化开发,顾名思义,就是将一个页面拆分成一个个小的功能块,每个功能块封装一个功能,完成属于 自己的独立的功能,然后连接起来,页面的管理和维护就更加简洁方便了。
最近在学习React的组件开发,于是就开始挑战一下一甜相机的模板页面进行React组件化开发,开发过程中还遇到了些问题,页面还有很多功能还未完善,现在只有一个雏形,大家就浅看一下吧,后续功能持续完善中~
组件展示
我需要开发的页面如上图所示,整个页面分为五部分,底部为固定的导航栏,页面为模板页面,主要由搜索框,图标,轮播图,图片链接以及双层嵌套的导航Tab组合成的页面展示
组件设计思路
-
底部栏:
Footer
使用 Router 进行设计,用Link
进行跳转 -
上部分搜索框: 点击跳转到搜索页面
-
轮播图: 用
Swiper
进行设计,自动轮播 -
图片导航:主要用
css
实现 -
双层tab嵌套切换:第二层tab导航显示由一个变量控制,大Tab为active状态时,则显示对应的第二层tab,每个小tab里面又会有个数据展示,将主页面传过来的数据进行筛选,然后显示在对应的小tab下
-
loading
状态:在进行异步数据拉取时,页面白屏显得性能不好,设置一个loading 状态,在数据拉取时显示,数据拉取成功后消失
组件介绍完毕,我们就来讲讲具体的封装过程吧。
组件封装
先对项目进行脚手架的建构,取名为react-camera
npm init @vitejs/app react-camera
在src 目录下创建四个文件夹
-
api文件夹用来存放与数据相关的链接,组件所有的数据将会在这一个文件夹下的request.js中使用ajax进行数据请求
-
assets 文件夹主要用来存放静态资源,像照片之类的资源
-
pages 文件夹用于放置与路由相关的页面,组件的底部栏是使用路由来配置的,底部栏的页面都将在pages里面展示
-
components 文件夹用来放置除去路由页面以外的组件
底部导航栏的封装
- 底部导航栏主要使用路由实现,需要安装一下包
npm i react-router react-router-dom
注意一下,实现路由必须在在入口文件main.jsx中用
Browserrouter
将包裹住,这样路由才能正常使用
底部导航栏包括四个页面,用路由实现,那么在pages文件夹下创建四个文件夹代表底部栏的四个页面,在App.jsx中引入 底部导航栏固定在在所有页面上,因此在components 文件夹下建立Footer 文件夹用来实现底部栏
import {Routes,Route} from 'react-router-dom'
import Tem from './pages/Tem'
import Vedio from './pages/Vedio'
import Pic from './pages/Pic'
import Kd from './pages/Kd'
import Header from './components/Header'
使用
Routes
和Route
,Route设置每个文件夹设置路由路径,Routes 是将所有的Route 组合成一个数组,方便管理
<div className="App">
<Header/>
<Routes>
<Route path="/temp" element={<Tem/>}></Route>
<Route path="/pz" element={<Pic/>}></Route>
<Route path="/vedio" element={<Vedio/>}></Route>
<Route path="/kd" element={<Kd/>}></Route>
</Routes>
<Footer/>
</div>
现在去Footer 文件夹下实现导航栏 ,路由有了路径还不够,要能实现跳转还需要使用
Link
import React from 'react'
import {FooterWrapper} from './style'
import {Link,useLocation} from 'react-router-dom'
import classnames from 'classnames'
const Footer =() => {
const {pathname} = useLocation()
if(['/select'].indexOf(pathname) != -1 ) return
return(
<FooterWrapper>
<Link to="/temp" className={classnames({active:pathname == '/temp'})}>
<span>模板</span>
</Link>
<Link to="/pz" className={classnames({active:pathname == '/pz'})}>
<span>拍照</span>
</Link>
<Link to="/vedio" className={classnames({active:pathname == '/vedio'})}>
<span>视频</span>
</Link>
<Link to="/kd" className={classnames({active:pathname == '/kd'})}>
<span>跟拍卡点</span>
</Link>
</FooterWrapper>
)
}
export default Footer
路由 acitve 状态的改变
const {pathname} = useLocation() // 将地址解构出来
-
要实现点击时Link 的
active
状态的改变,可以使用useLocation()
这个api , useLocation() 可以拿到当前页面的web地址,将pathname
从地址中解构出来,在className中判断pathname 是否为当前点击的地址路径 ,这样我们就实现了导航栏的切换 -
当然我们也要注意一下类名中的
classnames
,这是一个实用的工具库。传统的添加类名的方式 className={} 只能添加一个类名,而引入classnames这个工具库可以使className 同时添加多个类名
对于动态切换导航栏来说,引入classnames 可以更加简单的达到动态添加类名的效果。
className={classnames({active:pathname == '/pz'})}
实现路由切换的效果为:
模板页面的封装
四个部分分为四个组件进行封装 Tem模板页面则用来连接和展示组件
import React,{useState,useEffect} from 'react'
import Search from './search'
import Banners from './Banners'
import Tpmb from './Tpmb'
import Spmb from './Spmb'
// import Childrenrouter from './Childrenrouter'
import { getBanners, getTpmb, getSpmb } from '../../api/request'
import { Navbar ,Links} from './style'
import WeUI from 'react-weui'
const { Toast } = WeUI;
export default function Tem() {
const [showloading,setShowLoading] = useState(false)
const [showTpmb,setShowTpmb] = useState(false)
const [showSpmb,setShowSpmb] = useState(false)
const [tab ,setTab ] = useState('tpmb')
const [banners,setBanners] = useState([])
const [tpmbda,setTpmbda] = useState([])
const [spmbda,setSpmbda] = useState([])
useEffect(() =>{
(async()=>{
setShowLoading(true)
let {data:banner} = await getBanners();
setBanners(banner)
let { data:result} = await getTpmb()
setTpmbda(result)
let {data:spm} = await getSpmb()
setSpmbda(spm)
setShowLoading(false)
// console.log(data)
if(tab == "tpmb"){
setShowTpmb(true)
setShowSpmb(false)
}else if(tab == "spmb"){
setShowTpmb(false)
setShowSpmb(true)
}
})()
},[tab])
return (
<div>
<Search />
<Banners banners={banners}/>
<Navbar>
<div className="nav-links">
<ul >
<li><a className='lnk-xiutu' href="">修图</a></li>
<li><a className='lnk-pintu' href="">拼图</a></li>
<li><a className='lnk-erciyuan' href="">二次元脸</a></li>
<li><a className='lnk-tonghua' href="">童话脸</a></li>
</ul>
</div>
</Navbar>
<Links>
<ul >
<li>
<a className={tab=="tpmb"?'active':''} onClick={() =>setTab('tpmb')}>
<span>图片模板</span>
</a>
</li>
<li>
<a className={tab=="spmb"?'active':''} onClick={()=>setTab('spmb') }>
<span>视频模板</span>
</a>
</li>
</ul>
</Links>
<Toast icon="loading" show={showloading}>加载中</Toast>
{showTpmb && <Tpmb tpmbda={tpmbda} />}
{showSpmb && <Spmb spmbda={spmbda}/>}
</div>
)
}
数据请求功能
轮播图和双层嵌套Tab导航都需要数据请求,为了方便展示,我使用在线接口Mock工具fastmock 在线模拟ajax请求,实现前端的页面展示(这个工具十分方便,在没有后端程序的情况下可以实现ajax请求,有需要的小伙伴可以去尝试)
- 在数据请求api文件夹下的request.js 进行
axios 数据请求
import axios from 'axios'
export const getBanners = () =>
axios.get('https://www.fastmock.site/mock/61dc0b0b924d225e00742e9519eb4e55/beers/banners')
export const getSpmb = () =>
axios.get('https://www.fastmock.site/mock/61dc0b0b924d225e00742e9519eb4e55/beers/spmb')
export const getTpmb = ( ) =>
axios.get('https://www.fastmock.site/mock/61dc0b0b924d225e00742e9519eb4e55/beers/tpmb')
-
在主页面的UseEffect中使用
async + await
实现 同步使用数据数据拉取成功后,将数据作为变量传入对应组件
<Banners banners={banners}/>
loading的实现
数据拉取总是需要一些时间嘛,这时候屏幕一直白屏,用户体验感非常不好,于是我在数据请求的时候设置了一个
showloading
变量,来实现加载中的状态
- 首先需要安装weui 和 react-weui 这个组件库,将
Toast
从中解构出来,制作一个加载中的弹窗
<Toast icon="loading" show={showloading}>加载中</Toast>
(weui是微信官方制作的一个基础样式UI库,打造与原生微信同样的视觉和交互体验,里面有很多可以直接使用的样式框架,建议多多使用,组件开发中很多样式都可以在里面找到)
- 设置一个变量showloading,在请求数据前将showloading 状态变为true ,数据拉取结束后变回false
const [showloading,setShowLoading] = useState(false)
主要实现效果为:
轮播图的实现
轮播图则使用Swiper
进行实现
swiper 是一款轻量级的轮播图插件,可以支持pc端和移动端的使用,他可以快速的做出一个轮播图,十分方便快捷
实现效果图为:
useEffect(() =>{
new Swiper('.btn-banners',{
loop:true,
pagination:{
el:'.swiper-pagination' //幻灯片滑块
},
autoplay:{ //自动轮播
delay:3000
}
})
},[])
autoplay
属性实现图片的自动轮播
这个轮播图有一个需要注意一下:useEffect需要传一个空数组当第二个参数,如果不传,组件稍有变化,轮播图就会改变,导致轮播图的自动播放有点魔性,传空数组则表示轮播图的更新什么都不依赖。
搜索框的实现
搜索框主要由css 样式构成,增加了一个点击事件,点击进入搜索页面,点击取消则返回首页 ,这个是由路由实现的,在App.jsx 中新增一个searchK路由
<Route path="/select" element={<Searchk/>} />
返回首页用
Link
实现十分方便
<Link to="/temp"> <span className="im">取消</span></Link>
import React from 'react'
import { SearchWrapper,Tab } from './style'
import {Link} from 'react-router-dom'
export default function Search() {
return (
<SearchWrapper>
<Tab>
<Link to="/select">
<form action="">
<p>
<span className="bn">
<input type="submit" value="搜索"/>
</span>
<span className="inp"><input type="text" placeholder='珠光'></input></span>
<span className="im"></span>
</p>
</form>
</Link>
</Tab>
</SearchWrapper>
)
}
图片导航栏的实现
本次图片导航单纯用css 实现,只是一个样式,后续功能还在完善中~
功能:
-
导航栏的滑动: 使用
overflow:auto实现
,ul不要忘记display:flex
,否则无法滑动哈 -
图片的显示则是参考豆瓣页面导航,在li里添加背景图片,再将文字行高拉大,达到隐藏文字的效果
.lnk-xiutu {
background:transparent url('http://localhost:3000/src/assets/images/t1.jpg' )
}
像这样,
transparent url()
是为了将图片颜色显示出来 li中的样式background-size:cover;
则让背景图片缩放成背景区域大小
双层Tab导航切换的实现
由于开发组件的时候还没有学习二级路由,于是页面中的导航只能用tab 键来实现了,后续还会继续完善! 实现效果大概如下:
<Links>
<ul >
<li>
<a className={tab=="tpmb"?'active':''} onClick={() =>setTab('tpmb')}>
<span>图片模板</span>
</a>
</li>
<li>
<a className={tab=="spmb"?'active':''} onClick={()=>setTab('spmb') }>
<span>视频模板</span>
</a>
</li>
</ul>
</Links>
<Toast icon="loading" show={showloading}>加载中</Toast>
{showTpmb && <Tpmb tpmbda={tpmbda} />}
{showSpmb && <Spmb spmbda={spmbda}/>}
tab键active状态的改变
- 点击进行tab 键
active
的转换,下面的第二层导航栏则封装成一个组件<Tpmb />
和<Spmb />
声明两个变量showTpmb
和showSpmb
,在useEffect中监听tab的状态 ,tab改变则显示对应的组件
const [showTpmb,setShowTpmb] = useState(false)
const [showSpmb,setShowSpmb] = useState(false)
useEffect(()=>
if(tab == "tpmb"){
setShowTpmb(true)
setShowSpmb(false)
}else if(tab == "spmb"){
setShowTpmb(false)
setShowSpmb(true)
}
},[tab])
- active状态时下面下划线的改变 下划线主要使用伪元素制作而成
- 外层盒子使用
display:inline-block ;position:relative;
将盒子变为块级元素,并使子元素相对它定位 - 内层的a中对
active
状态进行设置,点击颜色和下划线发生改变
&.active::before{
content: " ";
color:red;
position: absolute;
left:25px ;
top: 0;
width: 50%;
height: 100%;
justify-content: center;
border-bottom: 2px solid silver;
}
&.active{
color:#000;
}
第二层导航栏的实现
数据由主页面中传进来 ,在这里根据第二层导航栏的tab切换进行数据筛选 ,再将图片显示在点击的选项下
这里要注意一点!!!
- 数据筛选需要在主页面中拉取传进来后再进行,否则第二层tab键
active
状态的改变无法传到父组件中,那么数据筛选就是无用的!- 导航栏中的
a
标签href
中需要加上“#” ,阻止a标签的默认行为发生,否则数据筛选后页面会直接跳转,无法长久显示!!
useEffect(() =>{
setShowLoading(true)
switch(tab1){
case"hot":
tpmbda =tpmbda.filter(item => item.type == "hot");
break;
case"new":
tpmbda = tpmbda.filter(item => item.type == "new");
break;
case"summy":
tpmbda= tpmbda.filter(item => item.type == "summy");
break;
default:
break;
}
setDa([...tpmbda])
},[tab1]
实现效果如下图:
结束语
至此一甜相机组件开发的模板页面样式已经大致出来,react组件中里面还有很多小细节功能还未完善,待我学成归来,我将继续完善这个页面,大家敬请期待~,感兴趣的话就点个小赞再走吧~
转载自:https://juejin.cn/post/7112755226028802056