前言
React能够把用户界面抽象成一个个组件,我们可以通过组合这些组件,最终得到功能丰富、可交互的页面。通过引入JSX语法,复用组件就变得非常容易,同时也可以保证组件结构的清晰。
项目准备
在构建react项目前,我们需要有好的开发环境。VSCode+Node.js就能够使我们开发项目更加直观,便利。在终端中通过命令行npm init @vitejs/app下载vite脚手架,根据提示便可快速初始化一个react项目。而在本项目开发中,需要以下依赖包:
-
axios:它是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get、post请求来获取后端数据。常搭配async函数实现数据异步同步化。
-
antd-mobile: 阿里蚂蚁集团基于React技术栈设计构建的的移动端开源组件库,可参照官方文档使用所需的组件。
-
font-awesome: 一种字体图标库,可指定大小,颜色及背景色。
-
styled-components: 即css in js,更好的实现了React的组件化思想样式。可以使用变量、继承,使用起来更自由,更灵活。
正文
首页展示
目录结构
- public文件夹:在该文件夹下创建js文件夹,存放自适应代码。在开发移动端项目时,不同类型的手机大小不一样,所以我们在开发过程中需要做自适应,可以用用rem来代替px。
var init = function () {
var clientWidth = document.documentElement.clientWidth
|| document.body.clientWidth;
if (clientWidth >= 640) {
clientWidth = 640;
}
var fontSize = 20 / 390 * clientWidth;
document.documentElement.style.fontSize = fontSize + "px";
}
init();
window.addEventListener("resize", init);
- api文件夹:存放数据接口的地方,数据请求统一放在这里,方便调用和管理。
- assets文件夹:存放静态资源,例如图片,音频,同时也可以做reset
- pages文件夹:页面级别组件存放地,即路由跳转显示的内容。
- components文件夹:普通组件,例如底部导航、播放音乐组件。
播放歌曲改变实现
该播放组件有两种状态,当歌曲列表中有歌曲时,显示正在播放的曲目当无歌曲时,切换成无歌曲状态。
<div className="play">
{list.length != 0 ? (
<div className="play_title">
<img src={PlayImage} />
<span>孤勇者 - 陈奕迅</span>
</div>
) : (
<div className="play_title">
<img src={NoplayImage} />
<span>酷狗音乐 就是歌多</span>
</div>
)}
<div className="play_next">
<i className="fa fa-play"></i>
<i className="fa fa-step-forward"></i>
<Space direction="vertical">
<i
className="fa fa-list-ul"
onClick={() => {
setVisible(true);
}}
></i>
歌曲弹窗功能实现
弹窗用的是antd-mobile里的Popup组件,效果如下:
import { Popup, Space } from "antd-mobile";
<i className="fa fa-step-forward"></i>
<Space direction="vertical">
<i
className="fa fa-list-ul"
onClick={() => {
setVisible(true);
}}
></i>
<Popup
visible={visible}
onMaskClick={() => {
setVisible(false);
}}
>
<div
style={{
height: "45vh",
overflowY: "scroll",
padding: "20px",
}}
>
{list.length != 0 ? (
<MusicList
list={list}
onDelete={onDelete}
onDeleteAll={onDeleteAll}
/>
) : (
<div>
<img src={Image1} />
</div>
)}
</div>
</Popup>
</Space>
删除播放列表实现
弹窗内容封装在MusicList组件中,父组件Play传送歌曲数据。可以删除单个播放歌曲和删除全部歌曲,当歌曲为0时,界面显示无播放歌曲。因截屏软件原因,看不到点击操作,全部清除提示是点击垃圾桶图标时触发。
删除操作
通过添加点击事件获取id,再利用filter过滤掉为该id的数据,同时将剩余数据传给list,以此达到删除功能。
const [visible, setVisible] = useState(false);
const [list, setList] = useState([]);
useEffect(() => {
(async () => {
let {data} = await getPopup();
setList(data);
})();
}, []);
// 删除播放列表歌曲
const onDelete = (id) => {
let newList = list.filter((item) => item.id != id);
setList(newList);
};
const onDeleteAll = () => {
setList([]);
};
清除提示功能
清除提示也用了antd-mobile的Modal, Toast, DotLoading。 Modal组件功能是当点击后弹出确定框,Toast组件是当点击确定后出现的效果,DotLoading组件则是起到删除操作的加载过程。
<i className="fa fa-random"></i>
<span>随机播放</span>
<span>{list.length}</span>
<div className="right">
<i
className="fa fa-trash"
onClick={() =>
Modal.confirm({
content: "清空播放队列",
onConfirm: async () => {
await sleep(2000);
Toast.show(
{
icon: "success",
content: "提交成功",
position: "bottom",
},
onDeleteAll()
);
},
})
}
></i>
其中的sleep函数如下:
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
{
<DotLoading color="primary" />;
}
resolve();
}, time);
});
}
底部导航功能实现
react中传统的添加类名只能有一个属性,而通过classnames可以添加多个类名。再根据从域名中解构出的pathname判断来添加active属性,适合tab切换动态添加类名。
import React from "react";
import { FooterWrapper } from "./style";
import { Link, useLocation } from "react-router-dom";
import classnames from "classnames";
export default function Footer() {
const { pathname } = useLocation();
return (
<FooterWrapper>
<Link
to="/listen"
className={classnames({
active: pathname == "/listen" || pathname == "/",
})}
>
<i className="fa fa-music"></i>
<span>首页</span>
</Link>
<Link
to="/watch"
className={classnames({ active: pathname == "/watch" })}
>
<i className="fa fa-play-circle-o"></i>
<span>直播</span>
</Link>
<Link
to="/sing"
className={classnames({ active: pathname == "/sing" })}
>
<i className="fa fa-microphone"></i>
<span>唱玩</span>
</Link>
<Link to="/me" className={classnames({ active: pathname == "/me" })}>
<i className="fa fa-user"></i>
<span>我的</span>
</Link>
</FooterWrapper>
);
}
路由跳转实现
通过路由可进行单页跳转。上述的底部导航跳转就对应路由路径。element是相应跳转之后的页面,放在pages文件夹中。
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import Listen from "../pages/Listen";
import Watch from "../pages/Watch";
import Sing from "../pages/Sing";
import Me from "../pages/Me";
import Rock from "../pages/Rock";
export default function RoutesConfig() {
return (
<Routes>
<Route path="/listen" element={<Listen />}></Route>
<Route path="/" element={<Navigate to="/listen" replacr={true} />}></Route>
<Route path="/watch" element={<Watch />}></Route>
<Route path="/sing" element={<Sing />}></Route>
<Route path="/me" element={<Me />}></Route>
<Route path="/rock" element={<Rock />}></Route>
</Routes>
);
}
轮播图实现
- 通过swiper,按照以下格式,即可实现轮播功能。其中autoplay为自动设置轮播时间。swiper-slide为轮播区域。
import Swiper from "swiper";
useEffect(() => {
new Swiper(".swiper-container", {
loop: true,
autoplay: {
delay: 3000,
},
});
}, []);
return(
<div className="swiper-container">
<div className="swiper-wrapper">
<div className="swiper-slide">
<img
width="100%"
src="https://y.qq.com/music/common/upload/MUSIC_FOCUS/4369086.jpg?max_age=2592000"
/>
</div>
</div>
</div>
)
专属推荐组件
- 通过添加点击事件,与状态,实现内容收起与打开。
import React, { useState } from "react";
import { Wrapper } from "./style";
export default function Recommend({ commend }) {
const [change, setChange] = useState(true);
return (
<Wrapper>
<div className="header" onClick={() => setChange(!change)}>
<div className={change ? "up" : "down"}>
<i className="fa fa-chevron-up"></i>
</div>
<p>专属推荐</p>
<div className="right">
<i className="fa fa-angle-right"></i>
</div>
</div>
<div>
{change &&
commend.map((item) => {
return (
<div className="content" key={item.id}>
<img src={item.img} />
<span>{item.title}</span>
<p>播放量:{item._count}</p>
</div>
);
})}
</div>
</Wrapper>
);
}