react hooks+redux+immutable.js打造网易云音乐精美webApp

你们好,我是神三元。css

上次出了服务端渲染的文章,得到了不少大佬的点赞评论,很是开心。以后一段时间有人问我为何忽然在掘金消失了,其实也并无,这个社区仍是常常在关注的,不过,更重要的是,这段时间淡出你们的视野,我决定开始沉淀一些技术上的东西,把以前学到的一些技术栈和对前端工程的一些思考作一个复盘和整合,因而,我开始了很早就有的想法,作一个音乐webApp吧。虽然如今已经有很多这样的webApp出现,可是实际上绝大部分vue的版本,即便有react的相似项目也中止维护了,hooks特性没来得及更新,整个开发的方式也不是我所喜欢的。不过请你放心,开始这样一个开源项目绝对不是把现有的项目拿来套套模板,我会长期来维护,并且,若是你能读完这篇文章或者阅读源码的话你能看到彻底不同的开发思路和开发方式,学到不少硬干货。html

好,如今正式来介绍这个项目。前端

在线体验地址vue

(目前掘金打开有一些样式问题,你们先用浏览器打开看吧,谢谢理解)react

移动端和PC端的chrome浏览器食用更佳 : )ios

源码附在最后, 注意查收。git

1、技术栈简介

前端部分:github

  • react v16.8全家桶(react,react-router) : 用于构建用户界面的 MVVM 框架
  • redux: 著名JavaScript状态管理容器
  • redux-thunk: 处理异步逻辑的redux中间件
  • immutable: Facebook历时三年开发出的进行持久性数据结构处理的库 (它和memo、Redux搭配就是神器,memo包裹函数组件跟PureComponent是同样的效果,在组件更新前进行数据的浅层比较,具体请参考这篇文章当 PureComponent 赶上 ImmutableJS)
  • react-lazyload: react懒加载库
  • better-scroll: 提高移动端滑动体验的知名库
  • styled-components: 处理样式,体现css in js的前端工程化神器(详情请移步我以前的文章styled-components:前端组件拆分新思路)
  • axios: 用来请求后端api的数据

后端部分:web

  • 采用github上妇孺皆知的网易云音乐NodeJS版api接口NeteaseCloudMusicApi,提供音乐数据。

其它:ajax

  • create-react-app: React脚手架,快速搭建项目
  • eslint: 知名代码风格检查工具
  • iconfont: 阿里巴巴图标库
  • fastclick: 解决移动端点击延迟300ms的问题

2、项目规范

在介绍项目功能以前,我有必要强调一个这个项目工程的开发规范和我我的的编码风格,提早告知一下,我这么作也是有本身充分的理由的,让项目可读性和可维护性尽量高,但愿后面看到一些奇葩的操做不要感到奇怪。

一、class组件再也不用,全面拥抱hooks,统一用函数组件。

二、组件内部状态用hooks处理,凡是业务数据所有放在redux中管理。

三、ajax请求以及后续数据处理的具体代码所有放在actionCreator中,由redux-thunk进行处理,尽量精简组件代码。

四、每个容器组件都有本身独立的reducer,而后再全局的store下经过redux的combineReducer方法合并。

五、JS变量名(包括函数名)采用小驼峰的方式,组件名或者styled-components导出的样式容器名都采用大驼峰,常量名全部字母大写。

六、普通CSS类名所有用英语小写,单词间用下划线链接,CSS动画钩子类名中单词用-链接。

七、凡是props中有数据的,所有在组件最前面提早解构赋值,而且,得到的属性名和方法名要分开声明,从父组件得到的props和经过react-redux中映射得到的props也要分开声明。

八、useEffect统一写在最前面,而且紧跟着props解构赋值代码后面。

九、凡是负责返回JSX的函数,统一汇集在函数最后面,中间不要穿插事件处理函数和其余逻辑。

十、mapDispatchToProps返回的函数中,函数名格式为xxxDispatch,以避免和现有action名冲突。

3、项目总体架构及演示演示

说明:本项目参考网易云音乐安卓端app界面开发,基础轮子组件没有借助任何UI框架,算是对本身的一个挑战,在这个过程也学到了很多设计经验。

因为传视频比较麻烦,可是图片又比较单调,没法体现这个webApp的动感,所以如下采用gif.

一、推荐部分

首页推荐:

推荐歌单详情:
空中切入切出效果,另外还有随着滑动会产生和标题跑马灯效果。 在歌单中歌曲数量过多的状况下,作了分页处理,随着滚动不断进行上拉加载,防止大量DOM加载致使的页面卡顿。

二、歌手部分

歌手列表:

这里作了异步加载的处理,上拉到底进行新数据的获取,下拉则进行数据的从新加载。

歌手详情:

三、排行榜

榜单页:

榜单详情:

四、播放器

播放器内核:

播放列表:

会有移动端app同样的反弹效果。

五、搜索部分

4、项目部分模块分享

一、利用better-scroll打造超级好用的scroll基础组件

import React, { forwardRef, useState,useEffect, useRef, useImperativeHandle } from "react"
import PropTypes from "prop-types"
import BScroll from "better-scroll"
import styled from 'styled-components';
import { debounce } from "../../api/utils";

const ScrollContainer = styled.div` width: 100%; height: 100%; overflow: hidden; `

const Scroll = forwardRef((props, ref) => {
  const [bScroll, setBScroll] = useState();

  const scrollContaninerRef = useRef();

  const { direction, click, refresh, pullUpLoading, pullDownLoading, bounceTop, bounceBottom } = props;

  const { pullUp, pullDown, onScroll } = props;

  useEffect(() => {
    const scroll = new BScroll(scrollContaninerRef.current, {
      scrollX: direction === "horizental",
      scrollY: direction === "vertical",
      probeType: 3,
      click: click,
      bounce:{
        top: bounceTop,
        bottom: bounceBottom
      }
    });
    setBScroll(scroll);
    if(pullUp) {
      scroll.on('scrollEnd', () => {
        //判断是否滑动到了底部
        if(scroll.y <= scroll.maxScrollY + 100){
          pullUp();
        }
      });
    }
    if(pullDown) {
      scroll.on('touchEnd', (pos) => {
        //判断用户的下拉动做
        if(pos.y > 50) {
          debounce(pullDown, 0)();
        }
      });
    }

    if(onScroll) {
      scroll.on('scroll', (scroll) => {
        onScroll(scroll);
      })
    }

    if(refresh) {
      scroll.refresh();
    }
    return () => {
      scroll.off('scroll');
      setBScroll(null);
    }
    // eslint-disable-next-line
  }, []);

  useEffect(() => {
    if(refresh && bScroll){
      bScroll.refresh();
    }
  })

  useImperativeHandle(ref, () => ({
    refresh() {
      if(bScroll) {
        bScroll.refresh();
        bScroll.scrollTo(0, 0);
      }
    }
  }));

  const PullUpdisplayStyle = pullUpLoading ? { display: "" } : { display: "none" };
  const PullDowndisplayStyle = pullDownLoading ? { display: "" } : { display: "none" };
  return (
    <ScrollContainer ref={scrollContaninerRef}> {props.children} {/* 滑到底部加载动画 */} <PullUpLoading style={ PullUpdisplayStyle }></PullUpLoading> {/* 顶部下拉刷新动画 */} <PullDownLoading style={ PullDowndisplayStyle }></PullDownLoading> </ScrollContainer>
  );
})

Scroll.defaultProps = {
  direction: "vertical",
  click: true,
  refresh: true,
  onScroll: null,
  pullUpLoading: false,
  pullDownLoading: false,
  pullUp: () => {},
  pullDown: () => {},
  bounceTop: true,
  bounceBottom: true
};

Scroll.propTypes = {
  direction: PropTypes.oneOf(['vertical', 'horizental']),
  refresh: PropTypes.bool,
  onScroll: PropTypes.func,
  pullUp: PropTypes.func,
  pullDown: PropTypes.func,
  pullUpLoading: PropTypes.bool,
  pullDownLoading: PropTypes.bool,
  bounceTop: PropTypes.bool,//是否支持向上吸顶
  bounceBottom: PropTypes.bool//是否支持向上吸顶
};




export default React.memo(Scroll);
复制代码

二、富有动感的loading组件

import React from 'react';
import styled, {keyframes} from 'styled-components';
import style from '../../assets/global-style'

const dance = keyframes` 0%, 40%, 100%{ transform: scaleY(0.4); transform-origin: center 100%; } 20%{ transform: scaleY(1); } `
const Loading = styled.div` height: 10px; width: 100%; margin: auto; text-align: center; font-size: 10px; >div{ display: inline-block; background-color: ${style["theme-color"]}; height: 100%; width: 1px; margin-right:2px; animation: ${dance} 1s infinite; } >div:nth-child(2) { animation-delay: -0.4s; } >div:nth-child(3) { animation-delay: -0.6s; } >div:nth-child(4) { animation-delay: -0.5s; } >div:nth-child(5) { animation-delay: -0.2s; } `

function LoadingV2() {
  return (
    <Loading> <div></div> <div></div> <div></div> <div></div> <div></div> <span>拼命加载中...</span> </Loading>
  );
}
 
export default LoadingV2;
复制代码

三、模块懒加载及代码分割(CodeSpliting)

react官方已经提供了相应的方案, 用react自带的lazy和Suspense便可完成。 操做以下:

import React, {lazy, Suspense} from 'react';
const HomeComponent = lazy(() => import("../application/Home/"));
const Home = (props) => {
  return (
    <Suspense fallback={null}> <HomeComponent {...props}></HomeComponent> </Suspense>
  )
};
......
export default [
  {
    path: "/",
    component: Home,
    routes: [
      {
        path: "/",
        exact: true,
        render:  ()=> (
          <Redirect to={"/recommend"}/> ) }, { path: "/recommend/", extra: true, key: 'home', component: Recommend, routes:[{ path: '/recommend/:id', component: Album, }] } ...... ] }, ]; 复制代码

5、将来规划和展望

目前这个项目的核心已经完成,可是仍是有不少扩展的余地,如今的模块至关于只是完成了60%吧。关于将来的规划,我是这么安排的:

  • 月底完成收藏、播放历史功能
  • 10月份以前完成登陆功能和评论模块
  • 10月中旬以前实现MV模块
  • 同时撰写《手摸手,一块儿用React实现网易云音乐webApp》系列拆解文章
  • 将来更多功能待补充...

因为还有其余的项目须要忙,因此作这个开源项目须要占掉我很大部分的空余时间,但我以为这是值得的,毕竟是对本身的一次锻炼和挑战。并且作这个项目的意义对我来讲,并不只仅在于完成这些功能,而是凝结着本身对于技术的思考,对以前各类想法的一次亲身实践。说句实在话,当项目在一个地方被卡住的时候,心里基本上是崩溃的,可是挺过去以后,发现本身又学会了很多东西,满满的成就感,这是我独立作开源项目比较深的感触。

最后,我要好好感谢那些帮助过个人人和项目,让我有底气开始作这个项目,克服一个个难关。

感谢黄轶前辈vue音乐实战课程,让我学到了很是多的原生JS技能和组件封装技巧。

感谢DellLeereact从入门到简书项目实战让我入门React,让我养成了React工程化的编码习惯。

感谢React开源项目mango-music,虽然我如今的项目和它在开发理念和编码风格上大相径庭,但仍是有部分的动画效果仍是借鉴了这个开源项目,让我大开眼界, 很是感谢,请你们也不忘去给这个项目点star,虽然没用到hooks,可是仍是值得一学的。

最后说明,这个项目毫不是一时的demo,我是会长期来维护,但愿你们能踊跃提pr,提issue,将这个项目打造的更加完美,可以帮助到更多的人学习到react除了官方demo以外的实际应用,避开更多的坑。

其实作这个项目出来效果虽然是还算天然,可是开发的过程是至关曲折的,我在后面也会作持续的分享,把个人开发过程遇到的挑战一五一十地分享给各位。最后,别忘了给这个项目点一个star哦,谢谢支持。

github源码地址

最后放上个人微信公众号:前端三元同窗

系列拆解文章将在公众号连载,敬请关注!
相关文章
相关标签/搜索