React组件开发:酷狗音乐功能实现

420 阅读4分钟

前言

React能够把用户界面抽象成一个个组件,我们可以通过组合这些组件,最终得到功能丰富、可交互的页面。通过引入JSX语法,复用组件就变得非常容易,同时也可以保证组件结构的清晰。

项目准备

在构建react项目前,我们需要有好的开发环境。VSCode+Node.js就能够使我们开发项目更加直观,便利。在终端中通过命令行npm init @vitejs/app下载vite脚手架,根据提示便可快速初始化一个react项目。而在本项目开发中,需要以下依赖包:

  1. axios:它是一个基于 promise 的 HTTP 库,简单的讲就是可以发送get、post请求来获取后端数据。常搭配async函数实现数据异步同步化。

  2. antd-mobile: 阿里蚂蚁集团基于React技术栈设计构建的的移动端开源组件库,可参照官方文档使用所需的组件。

  3. font-awesome: 一种字体图标库,可指定大小,颜色及背景色。

  4. styled-components: 即css in js,更好的实现了React的组件化思想样式。可以使用变量、继承,使用起来更自由,更灵活。

正文

首页展示

KuGou1.png

目录结构

目录.png

  • 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);
  1. api文件夹:存放数据接口的地方,数据请求统一放在这里,方便调用和管理。

API接口.png

  1. assets文件夹:存放静态资源,例如图片,音频,同时也可以做reset
  2. pages文件夹:页面级别组件存放地,即路由跳转显示的内容。
  3. 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组件,效果如下:

弹出.gif

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时,界面显示无播放歌曲。因截屏软件原因,看不到点击操作,全部清除提示是点击垃圾桶图标时触发。

删除11.gif 删除操作

通过添加点击事件获取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切换动态添加类名。 跳转2.gif

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>
    
  );
}

轮播图实现

轮播图.gif

  • 通过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>
  )

专属推荐组件

推荐gif.gif

  • 通过添加点击事件,与状态,实现内容收起与打开。
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>
  );
}

源码地址

Traveler-101/KuGou2.0 (github.com)