{"id":13793677,"url":"https://github.com/sanyuan0704/react-cloud-music","last_synced_at":"2025-05-15T08:00:22.552Z","repository":{"id":38291174,"uuid":"201892304","full_name":"sanyuan0704/react-cloud-music","owner":"sanyuan0704","description":"React 16.8打造精美音乐WebApp","archived":false,"fork":false,"pushed_at":"2024-06-04T09:44:26.000Z","size":3667,"stargazers_count":1957,"open_issues_count":37,"forks_count":537,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-05-13T22:09:35.705Z","etag":null,"topics":["axios","better-scroll","immutable","react","react-hooks","react-redux","react-router-v4","redux","redux-immutable","redux-thunk","styled-components"],"latest_commit_sha":null,"homepage":"https://sanyuan0704.github.io/react-cloud-music/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sanyuan0704.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-12T08:47:36.000Z","updated_at":"2025-05-07T03:24:34.000Z","dependencies_parsed_at":"2024-09-21T02:00:50.570Z","dependency_job_id":"1b8db3b5-2ce2-41b2-b04e-5ce9f340385f","html_url":"https://github.com/sanyuan0704/react-cloud-music","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuan0704%2Freact-cloud-music","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuan0704%2Freact-cloud-music/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuan0704%2Freact-cloud-music/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sanyuan0704%2Freact-cloud-music/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sanyuan0704","download_url":"https://codeload.github.com/sanyuan0704/react-cloud-music/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254301420,"owners_count":22047901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axios","better-scroll","immutable","react","react-hooks","react-redux","react-router-v4","redux","redux-immutable","redux-thunk","styled-components"],"created_at":"2024-08-03T23:00:28.871Z","updated_at":"2025-05-15T08:00:21.669Z","avatar_url":"https://github.com/sanyuan0704.png","language":"JavaScript","funding_links":[],"categories":["目录"],"sub_categories":["\u003ca id=\"project\"\u003e项目\u003c/a\u003e"],"readme":"# react hooks+redux+immutable.js仿网易云音乐打造精美webApp\n\n\n系列拆解文章已经出炉，整理成了掘金小册，请点[这里](https://juejin.im/book/5da96626e51d4524ba0fd237)查看。如遇到问题，或者需要联系加群，请加微信: `FESanyuan`。\n\n移动端和PC端的chrome浏览器食用更佳 : )\n\n打开方式:\n1. 将项目 clone 下来\n```shell\n$ git clone https://github.com/sanyuan0704/cloud-music.git\n$ cd cloud-music\n$ npm install\n\n// 下载子模块\n$ git submodule update --init --recursive\n$ cd NeteaseCloudMusicApi\n$ npm install \n$ cd ../  (注意: 一定要返回到上一层)\n```\n接下来，要记得把`src/api/config.js`中把`baseUrl`改成接口的地址。（一定要记得,不然报404!）\n\n2. 运行\n```shell\n$ npm run start\n```\n\n现在就在本地的3000端口访问了。如果要打包到线上，执行`npm run build`即可。\n\n\n项目介绍:\n\n说明:本项目参考网易云音乐安卓端app界面开发，基础UI绝大多数自己来构建，算是对自己的一个挑战，在这个过程也学到了不少设计经验。\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c80048984d1af3?w=1423\u0026h=1092\u0026f=png\u0026s=407282)\n\n### 功能介绍\n\n#### 1、推荐部分\n\n首页推荐:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f735b83a0d15?w=372\u0026h=668\u0026f=gif\u0026s=2856467)\n\n推荐歌单详情:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f75ca0469552?w=372\u0026h=668\u0026f=gif\u0026s=1862466)\n\n空中切入切出效果，另外还有随着滑动会产生和标题跑马灯效果。\n在歌单中歌曲数量过多的情况下，做了分页处理，随着滚动不断进行上拉加载，防止大量DOM加载导致的页面卡顿。\n\n#### 2、歌手部分\n歌手列表:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f793e8a1524b?w=372\u0026h=668\u0026f=gif\u0026s=1224668)\n\n这里做了异步加载的处理，上拉到底进行新数据的获取，下拉则进行数据的重新加载。\n\n歌手详情:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f7ea74fffa11?w=372\u0026h=668\u0026f=gif\u0026s=2435912)\n\n\n#### 3、排行榜\n\n榜单页:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f811ec0f7375?w=372\u0026h=668\u0026f=gif\u0026s=2334445)\n\n榜单详情:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f82639a1dc34?w=372\u0026h=668\u0026f=gif\u0026s=2162917)\n\n#### 4、播放器\n\n播放器内核:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f8a5687ebb93?w=372\u0026h=668\u0026f=gif\u0026s=3339773)\n\n播放列表:\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c7f98711c43ae3?w=372\u0026h=667\u0026f=gif\u0026s=2223620)\n\n会有移动端app一样的反弹效果。\n\n#### 5、搜索部分\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c804bd87a2dbbe?w=372\u0026h=667\u0026f=gif\u0026s=1275414)\n\n\n### 项目部分模块分享\n\n#### 1、利用better-scroll打造超级好用的scroll基础组件\n\n```js\nimport React, { forwardRef, useState,useEffect, useRef, useImperativeHandle } from \"react\"\nimport PropTypes from \"prop-types\"\nimport BScroll from \"better-scroll\"\nimport styled from 'styled-components';\nimport { debounce } from \"../../api/utils\";\n\nconst ScrollContainer = styled.div`\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n`\n\nconst Scroll = forwardRef((props, ref) =\u003e {\n  const [bScroll, setBScroll] = useState();\n\n  const scrollContaninerRef = useRef();\n\n  const { direction, click, refresh, pullUpLoading, pullDownLoading, bounceTop, bounceBottom } = props;\n\n  const { pullUp, pullDown, onScroll } = props;\n\n  useEffect(() =\u003e {\n    if(bScroll) return;\n    const scroll = new BScroll(scrollContaninerRef.current, {\n      scrollX: direction === \"horizental\",\n      scrollY: direction === \"vertical\",\n      probeType: 3,\n      click: click,\n      bounce:{\n        top: bounceTop,\n        bottom: bounceBottom\n      }\n    });\n    setBScroll(scroll);\n    if(pullUp) {\n      scroll.on('scrollEnd', () =\u003e {\n        //判断是否滑动到了底部\n        if(scroll.y \u003c= scroll.maxScrollY + 100){\n          pullUp();\n        }\n      });\n    }\n    if(pullDown) {\n      scroll.on('touchEnd', (pos) =\u003e {\n        //判断用户的下拉动作\n        if(pos.y \u003e 50) {\n          debounce(pullDown, 0)();\n        }\n      });\n    }\n\n    if(onScroll) {\n      scroll.on('scroll', (scroll) =\u003e {\n        onScroll(scroll);\n      })\n    }\n\n    if(refresh) {\n      scroll.refresh();\n    }\n    return () =\u003e {\n      scroll.off('scroll');\n      setBScroll(null);\n    }\n    // eslint-disable-next-line\n  }, []);\n\n  useEffect(() =\u003e {\n    if(refresh \u0026\u0026 bScroll){\n      bScroll.refresh();\n    }\n  })\n\n  useImperativeHandle(ref, () =\u003e ({\n    refresh() {\n      if(bScroll) {\n        bScroll.refresh();\n        bScroll.scrollTo(0, 0);\n      }\n    }\n  }));\n\n  const PullUpdisplayStyle = pullUpLoading ? { display: \"\" } : { display: \"none\" };\n  const PullDowndisplayStyle = pullDownLoading ? { display: \"\" } : { display: \"none\" };\n  return (\n    \u003cScrollContainer ref={scrollContaninerRef}\u003e\n      {props.children}\n      {/* 滑到底部加载动画 */}\n      \u003cPullUpLoading style={ PullUpdisplayStyle }\u003e\u003c/PullUpLoading\u003e\n      {/* 顶部下拉刷新动画 */}\n      \u003cPullDownLoading style={ PullDowndisplayStyle }\u003e\u003c/PullDownLoading\u003e\n    \u003c/ScrollContainer\u003e\n  );\n})\n\nScroll.defaultProps = {\n  direction: \"vertical\",\n  click: true,\n  refresh: true,\n  onScroll: null,\n  pullUpLoading: false,\n  pullDownLoading: false,\n  pullUp: () =\u003e {},\n  pullDown: () =\u003e {},\n  bounceTop: true,\n  bounceBottom: true\n};\n\nScroll.propTypes = {\n  direction: PropTypes.oneOf(['vertical', 'horizental']),\n  refresh: PropTypes.bool,\n  onScroll: PropTypes.func,\n  pullUp: PropTypes.func,\n  pullDown: PropTypes.func,\n  pullUpLoading: PropTypes.bool,\n  pullDownLoading: PropTypes.bool,\n  bounceTop: PropTypes.bool,//是否支持向上吸顶\n  bounceBottom: PropTypes.bool//是否支持向上吸顶\n};\n\nexport default React.memo(Scroll);\n```\n#### 2、富有动感的loading组件\n\n```js\nimport React from 'react';\nimport styled, {keyframes} from 'styled-components';\nimport style from '../../assets/global-style'\n\nconst dance = keyframes`\n    0%, 40%, 100%{\n      transform: scaleY(0.4);\n      transform-origin: center 100%;\n    }\n    20%{\n      transform: scaleY(1);\n    }\n`\nconst Loading = styled.div`\n    height: 10px;\n    width: 100%;\n    margin: auto;\n    text-align: center;\n    font-size: 10px;\n    \u003ediv{\n      display: inline-block;\n      background-color: ${style[\"theme-color\"]};\n      height: 100%;\n      width: 1px;\n      margin-right:2px;\n      animation: ${dance} 1s infinite;\n    }\n    \u003ediv:nth-child(2) {\n      animation-delay: -0.4s;\n    }\n    \u003ediv:nth-child(3) {\n      animation-delay: -0.6s;\n    }\n    \u003ediv:nth-child(4) {\n      animation-delay: -0.5s;\n    }\n    \u003ediv:nth-child(5) {\n      animation-delay: -0.2s;\n    } \n`\n\nfunction LoadingV2() {\n  return (\n    \u003cLoading\u003e\n      \u003cdiv\u003e\u003c/div\u003e\n      \u003cdiv\u003e\u003c/div\u003e\n      \u003cdiv\u003e\u003c/div\u003e\n      \u003cdiv\u003e\u003c/div\u003e\n      \u003cdiv\u003e\u003c/div\u003e\n      \u003cspan\u003e拼命加载中...\u003c/span\u003e\n    \u003c/Loading\u003e\n  );\n}\n \nexport default LoadingV2;\n```\n\n![](https://user-gold-cdn.xitu.io/2019/8/11/16c801f8bc254d65?w=250\u0026h=35\u0026f=gif\u0026s=20097)\n\n#### 3、模块懒加载及代码分割(CodeSpliting)\nreact官方已经提供了相应的方案, 用react自带的lazy和Suspense即可完成。\n操作如下:\n```js\nimport React, {lazy, Suspense} from 'react';\nconst HomeComponent = lazy(() =\u003e import(\"../application/Home/\"));\nconst Home = (props) =\u003e {\n  return (\n    \u003cSuspense fallback={null}\u003e\n      \u003cHomeComponent {...props}\u003e\u003c/HomeComponent\u003e\n    \u003c/Suspense\u003e\n  )\n};\n......\nexport default [\n  {\n    path: \"/\",\n    component: Home,\n    routes: [\n      {\n        path: \"/\",\n        exact: true,\n        render:  ()=\u003e (\n          \u003cRedirect to={\"/recommend\"}/\u003e\n        )\n      },\n      {\n        path: \"/recommend/\",\n        extra: true,\n        key: 'home',\n        component: Recommend,\n        routes:[{\n          path: '/recommend/:id',\n          component: Album,\n        }]\n      }\n      ......\n    ]\n  },\n\n];\n```\n\n### 未来规划和展望\n目前这个项目的核心已经完成，但是还是有很多扩展的余地。关于未来的规划，我是这么安排的:\n\n- 完成收藏、播放历史功能\n- 完成登录功能和评论模块\n- 实现MV模块\n- 同时撰写《手摸手，一起用React实现网易云音乐webApp》系列拆解文章\n- 未来更多功能待补充...\n\n这个项目长期维护，希望大家踊跃提issue和pr，把这个项目打造得更加完美，帮助到更多的react开发者！\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsanyuan0704%2Freact-cloud-music","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsanyuan0704%2Freact-cloud-music","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsanyuan0704%2Freact-cloud-music/lists"}