Разработка проекта «NetEase Cloud Music PC» на базе корзины семейства React (2)

React.js
Разработка проекта «NetEase Cloud Music PC» на базе корзины семейства React (2)

предисловие

В этой статье начинается проект "NetEase Cloud Music PC". Рекомендуется иметь следующие основы.react、redux、redux-thunk、react-router,предыдущая главаЭто просто предварительное введение в проект, в этой главе вы должны завершить: базовую скелетную структуру NetEase Cloud и завершить использованиеredux-immutableрефакторингredux

Результаты этой главы следующие

Инициализация проекта

Предисловие — плагин vscode и chrome (необязательно)

  • Если вы уже установили его, вы можете пропустить его.Следующее является необязательным.Конечно, можно не устанавливать его.

  • Для более удобной разработки проектов рекомендуется установить следующиеvscodeплагин

    • ESLint: инструмент проверки стиля кода, помогающий нам стандартизировать написание кода.

    • vscode-styled-components: существуетjsнаписано наstyled-componentsСредняя подсветка синтаксиса и intellisense

    • path-alias: Путь псевдонима имеет соответствующую смарт-подсказку

    • ES7 React/Redux/GraphQL/React-Native snippets: сегмент кода

  • chromeплагин

    • Redux DevTools: легко отлаживатьreduxданные
    • FeHelper: вернулся на серверjsonданные украшают

Предисловие - предварительный просмотр проекта и исходный код

  • Адрес онлайн-просмотра 👉:www.wanguancs.top

  • проектGihubадрес 👉: Musci 163Если вы считаете проект неплохим 👏, поставьте звездочку ⭐, чтобы поддержать его~

1. Отдел каталогов проектов

  • использоватьcreate-react-appСтруктура проекта инициализации строительных лесов:create-react-app music163_xxx
  • Структуру каталогов также можно разделить в соответствии со структурой, к которой вы привыкли.
│─src
  ├─assets 存放公共资源css和图片
    ├─css  全局css
    ├─img  
  ├─common  公共的一些常量
  ├─components 公共组件
  ├─pages   路由映射组件
  ├─router  前端路由配置
  ├─service 网络配置和请求
  └─store   全局的store配置
  └─utils   工具函数
  └─hooks   自定义hook

2. Выбор стиля проекта

  • Выбор сброса стиля элемента:
    • reset.css
    • normalize.css + custom.css(то есть обычайcss)
  • Установитьnormalize.css: yarn add normalize.css
    • глобальноcssИмпорт файла:src->assets->css-> normalize.css
    • Скачать первымРесурсы проекта (все фоновые изображения и спрайты, используемые проектом)
      • Если скачатьgithubФайл работает медленно, пожалуйста, обратитесь к моемуэта статьяУскорить 🚀 загрузку файлов
    • глобальный нижеCSSдля инициализации страницы, если вашcssЕсли вы хорошо разбираетесь в этом, то рекомендуется скопировать его напрямую 😏
      • будет ниже👇cssКопировать в глобальный пользовательскийcssв файле (src -> assets -> css -> reset.css)
      • Многие определения - это некоторые фоны спрайтов
      • Имя класса спрайта соответствует имени файла изображения.
/* reset.css (自定义的css) */
@import '~normalize.css';
/* 后续有说明,先跳过即可(安装完antd再导入的) */
/* @import '~antd/dist/antd.css'; */

/* 样式的重置 */
body, html, h1, h2, h3, h4, h5, h6, ul, ol, li, dl, dt, dd, header, menu, section, p, input, td, th, ins {
  padding: 0;
  margin: 0;
}

ul, ol, li {
  list-style: none;
}

a {
  text-decoration: none;
  color: #666;
}

a:hover {
  color: #666;
  text-decoration: underline;
}

i, em {
  font-style: normal;
}

input, textarea, button, select, a {
  outline: none;
  border: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

img {
  border: none;
  vertical-align: middle;
}

/* 全局样式 */
body, textarea, select, input, button {
  font-size: 12px;
  color: #333;
  font-family: Arial, Helvetica, sans-serif;
  background-color: #f5f5f5;
}

.text-nowrap {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.w1100 {
  width: 1100px;
  margin: 0 auto;
}

.w980 {
  width: 980px;
  margin: 0 auto;
}

.text-indent {
  text-indent: -9999px;
}

.inline-block {
  display: inline-block;
}

.sprite_01 {
  background: url(../img/sprite_01.png) no-repeat 0 9999px;
}

.sprite_02 {
  background: url(../img/sprite_02.png) no-repeat 0 9999px;
}

.sprite_cover {
  background: url(../img/sprite_cover.png) no-repeat 0 9999px;
}

.sprite_icon {
  background: url(../img/sprite_icon.png) no-repeat 0 9999px;
}

.sprite_icon2 {
  background: url(../img/sprite_icon2.png) no-repeat 0 9999px;
}

.sprite_button {
  background: url(../img/sprite_button.png) no-repeat 0 9999px;
}

.sprite_button2 {
  background: url(../img/sprite_button2.png) no-repeat 0 9999px;
}

.sprite_table {
  background: url(../img/sprite_table.png) no-repeat 0 9999px;
}

.my_music {
  background: url(../img/mymusic.png) no-repeat 0 9999px;
}

.not-login {
  background: url(../img/notlogin.jpg) no-repeat 0 9999px;
}

.image_cover {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  text-indent: -9999px;
  background: url(../img/sprite_cover.png) no-repeat -145px -57px;
}

.sprite_player {
  background: url(../img/playbar_sprite.png) no-repeat 0 9999px;
}

.lyric-css .ant-message-notice-content {
  position: fixed;
  left: 50%;
  bottom: 50px;
  transform: translateX(-50%);
  background-color: rgba(0,0,0,.5);
  color: #f5f5f5;
}

.wrap-bg2 {
  background: url(../img/wrap3.png) repeat-y center 0;;
}

3. Настройте псевдоним пути

  • Шаг 1: Установите craco:

    • yarn add @craco/craco
  • Шаг 2: Изменитьpackage.jsonдокумент

    • Изначально, когда мы начинали, мы прошлиreact-scriptsсправляться;

    • Начиная сейчас, мы проходимcracoсправляться;

    "scripts": {
    -"start": "react-scripts start",
    -"build": "react-scripts build",
    -"test": "react-scripts test",
    
    \+ "start": "craco start",
    \+ "build": "craco build",
    \+ "test": "craco test",
    }
    
  • Шаг третий: вв корневом каталогеСоздайтеcraco.config.jsФайл используется для изменения конфигурации по умолчанию↓

    • module.exports = { // 配置文件 }
// 根路径 -> craco.config.js
const path = require('path')
const resolve = dir => path.resolve(__dirname, dir)

module.exports = {
  webpack: {
    alias: {
       // @映射src路径
      '@': resolve('src'),
      'components': resolve('src/components')
    }
  }
}

Директировка структуры проекта

компонент заголовка

  • условие: исправлено, не меняется с URL

  • хранение компонентов: в папке src/"components/app-header"

  • Нажмите, чтобы просмотретьполный эффект

компонент нижнего колонтитула

  • условие: исправлено, не меняется с URL

  • хранение компонентов: в папке src/"components/app-footer"

  • Нажмите, чтобы просмотретьполный эффект

основное содержание тела

  • условие:Основной контент будет динамически меняться по мере изменения пути
  • использоватьrouterДинамический рендерингpathСоответствующие компоненты, конкретная конфигурация выглядит следующим образом↓
    • Помещение: вsrc/pagesпапка созданаdiscover和mine和friendкомпоненты
  1. Установитьrouter:yarn add react-router-dom

  2. Установитьreact-router-config(Карта маршрутов централизованной конфигурации):yarn add react-router-config

    // src/router->index.js  (配置路由映射)
    import { Redirect } from "react-router-dom";
    import Discover from "@/pages/discover";
    import Mine from "@/pages/mine";
    import Friend from "@/pages/friend";
    
    const routes = [
      {
        path: "/discover",
        component: Discover    
      },
      {
        path: "/mine",
        component: Mine
      },
      {
        path: "/friend",
        component: Friend
      },
    ];
    
    export default routes;
    
  3. существуетApp.jsиспользоватьHashRouterИспользование пакета компонентовrouter-configНастроенная карта маршрутов (чтобы конфигурация таблицы карты маршрутов вступила в силу):

    • Нажмите, чтобы просмотреть конфигурацию в App.js
    • Убедитесь, что маршрут настроен успешно:существуетheaderкомпонент, использованиеNavLinkТестовое переключение путей и рендеринг соответствующих компонентов

    • Нажмите, чтобы просмотретьполный эффект

Завершенный эффект выглядит следующим образом👇

  • основное содержание следоватьURLПроисходят изменения, обратите внимание на изменения путей и переключения компонентов



Компонент заголовка заголовка

1. Написание стиля компонента заголовка

  • дляПредотвращение конфликтов стилей в нескольких компонентах, использовать стили внутри компонентаstyled-componentsбиблиотека
  • Установить с помощью:yarn add styled-components
  • Использование макета:Flex

2. Отделение области головы

3. Реализация и идеи зоны головы (слева)

header-item-active

实现功能:点击头部列表项,添加背景实现高亮和下面的小三角
实现思路:(利用`NavLink`组件被点击有`active`的`className`单独给class设置样式即可)
   1.NavLink点击活跃后实现上面的效果
   2.给NavLink设置自定义className,在对应的css文件实现效果

4. Реализация и идеи зоны головы (справа)

  • в наличии правая сторонаAntdКомпоненты также могут быть написаны сами по себе
  • УстановитьAnt design: yarn add antd
  • УстановитьAnt design icons: yarn add @ant-design/icons
1.在reset.css文件引入: antd样式 ↓
    @import '~antd/dist/antd.css';
2.在Header.js引入icons
3.使用antd组件: Input组件
4.修改placehold文本样式
  • Примечание: поиск справа键盘图标Я добавил это позже, вы можете временно пропустить это, и друзья, которые заинтересованы, могут это сделать.

Нижний компонент нижнего колонтитула

1. Схема нижней области

2. Осознайте эффект


Оптимизация маршрутизации и описание API

1. Документация по интерфейсу проекта

2. Оптимизация маршрута_перенаправление

  • Перенаправьте «корневой маршрут» на:discoverстраница
//  src/router/router.js -> 对根路径进行重定向到: /discover   👇
const routes = [
    //   `/`根路径重定向到: /discover路径
-->  { path: '/', exact: true, render: () => <Redirect to="/discover" /> },  <---
     { path: '/discover', component: JMDiscover }
    //  ...
]

3. Вложенная маршрутизация

отдел компоновки

Создайте соответствующие подкомпоненты

Создайте соответствующие подкомпоненты в папке Discover.

Настройте "таблицу сопоставления вложенных маршрутов"

const routes = [
  { path: '/', exact: true, render: () => <Redirect to="/discover" /> },
  {
    path: '/discover',
    component: JMDiscover,
---> routes: [
      { path: '/discover', render: () => <Redirect to="/discover" /> },
      { path: '/discover/recommend', component: JMRecommend },
      { path: '/discover/ranking', component: JMRanking },
      { path: '/discover/album', component: JMAlbum },
      { path: '/discover/djradio', component: JMDjradio },
      { path: '/discover/artist', component: JMArtist },
      { path: '/discover/songs', component: JMSongs }
    ],  <---
  },
  { path: '/mine', component: JMMine },
  { path: '/friend', component: JMFriend },
]

отображать конфигурацию вложенного подмаршрута

  • существуетdiscoverОтображение вложенных подмаршрутов под страницей
// src->pages->discover->index.js
import { renderRoutes } from 'react-router-config'
export default memo(function JMDiscover(props) {
  const { route } = props
    return (
    <div>
       ...
      {renderRoutes(route.routes)}
    </div>
  )
})

Полный эффект↓

4. API карусели

  • Начните отправлять сетевые запросы, используяaxios

  • Установить аксиомы:yarn add axios

    • существуетsrcновая папкаserviceПапка 📂 (назовите ее в соответствии с вашими привычками), используемая для сетевых запросов, связанных с

    • Предпосылка: будетaxiosУпакованные файлы копируются в эту папку

    • Если вы еще не упаковали его раньше, вы можете обратиться к моемуaxiosПростой пакет выглядит следующим образом👇

    • Простой пакет Axios, нажмите для просмотра
  • Теперь начнем запрашивать данные карусели:


redux сохраняет данные, возвращаемые сервером

1. Установите редукс

  • Установить:
    • yarn add redux
    • yarn add react-redux
    • yarn add redux-thunk
  • Объединить установку:yarn add redux react-redux redux-thunk
структура каталогов организации

2. Настройте избыточность

store → reducer.js в корневом каталоге проекта src

import { combineReducers } from "redux";
// 引入recommend页面的store(下面可以暂时不写,跳到下第3小结)
import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store'

// 将多个reducer合并
const cRducer = combineReducers({
   // 下面可以暂时不写(下面可以暂时不写,跳到下第3小结)
  recommend: recommendReducer
})
export default cRducer

Хранить → index.js в корне проекта src

import { createStore, applyMiddleware, compose } from "redux";
// 引入thunk中间件(可以让派发的action可以是一个函数)
import thunk from 'redux-thunk'
// 引入合并后的reducer
import cRducer from "./reducer";
// redux-devtools -> 浏览器插件
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// 创建store并传递: 1.reducer(纯函数) 2.StoreEnhancer
const store = createStore(cRducer, composeEnhancers(
  applyMiddleware(thunk)
))

export default store

В файле app.js в корневом каталоге src проекта → настройте react-redux

// 在App.js组件中使用react-redux
import { Provider } from 'react-redux'
import { renderRoutes } from 'react-router-config'
import store from './store'
export default memo(function App() {
  return (
    <Provider store={store}>
       {/* ... */}
       {renderRoutes(routes)}
    </Provider>
  )
})

резюме

  1. СоздайтеcombinReducers
    • используется для объединения несколькихreducerсливаться
  2. Создайтеstore
    • настроить промежуточное ПО иredux-devtools
  3. настроитьreact-redux
    • Помогите нам подключитьсяredux

3. Данные карусели запрашиваются через redux-thunk

  • Интерфейс API карусельных данных:
  • Поместите запрошенные данные карусели вreduxсреди
  • Обратите внимание на путь к файлу в комментариях к коду.
// src->page->dicover->child-pages->recommend->store->actionCreator.js (派发action用的)
import * as actionTypes from './actionTypes'
import { getTopBanners } from '@/service/recommend.js'
// 轮播图Action
export const changeTopBannerAction = res => ({
  type: actionTypes.CHANGE_TOP_BANNER,
  topBanners: res,
})
// 轮播图网络请求
export const getTopBannersAction = () => {
  return dispatch => {
    // 发送网络请求
    getTopBanners().then(res => {
      dispatch(changeTopBannerAction(res))
    })
  }
}

// ---src/service->recommend.js-----------推荐页的轮播图API接口👇-----------------------
import request from './request'
export function getTopBanners() {
  return request({
    url: "/banner"
  })
}

// ---src/page->dicover->child-pages->recommend.js ---------------------------------
import React, { memo, useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getTopBannersAction } from './store/actionCreator'

function JMRecommend(props) {
  // redux Hook 组件和redux关联: 获取数据和进行操作
  const { topBanners } = useSelector(state => ({
    topBanners: state.recommend.topBanners,
  }))
  const dispatch = useDispatch()
  
  useEffect(() => {
    dispatch(getTopBannersAction())
  }, [dispatch])

  return (
    <div>
      <h2>JMRecommend</h2>
      <h3>{topBanners.length}</h3>
    </div>
  )
}
export default memo(JMRecommend)

4. оптимизация производительности useSelector

// src/page->dicover->child-pages->recommend.js👇
---> import { shallowEqual, useDispatch, useSelector } from 'react-redux'  <---

// --->: 代表发生了变化的标识 <---
const { topBanners } = useSelector(state => ({
  topBanners: state.recommend.topBanners,
---> }), shallowEqual)  <---

Сочетание ImmutableJs

Введение и установка immutableJS

  • Знакомство с immutableJS: использованиеImmutableможет позволитьreduxподдерживается вstateВместо поверхностного копирования и переназначения используйтеImmutableструктура данных для хранения данных
  • Использовать преимущества Immutablejs: модифицированоstateОригинальная структура данных не будет изменена, но новая модифицированная структура данных будет возвращена. Предыдущая структура данных может быть использована без потратительной памяти.
  • immutableJSУстановить:yarn add immutable

Управляйте данными с помощью Redux

  1. Используйте combReducers в redux-immutable;

  2. Все данные в редукторе преобразуются в неизменяемые данные типа

Immutablejs Интеграционный проект

Используйте ImmutableJS для редуктора текущего каталога проекта. image-20200927215952708
// 1.在reducer.js文件使用Immutable设置: discover->child-cpn->recommend->store->reducer.js
/* --> */ import { Map } from "immutable";  //<---
import * as actionTypes from './actionTypes'

// 使用Immutable管理redux中的state (修改的`state`不会修改原有数据结构, 而是返回修改后新的数据结构)
const defaultState = Map({
  topBanners: [],
})

export default function reducer(state = defaultState, action) {
  switch (action.type) {
    case actionTypes.CHANGE_TOP_BANNER:
/* ---> */   return state.set('topBanners', action.topBanners)  //<---
    default:
      return state
  }
}


// 2.在recommend的index.js文件获取的是Immutable对象, 需要进行设置
const { topBanners } = useSelector(state => ({
--->   topBanners: state.recommend.get('topBanners')  <---
  }))

redux-Immutable

  • Почему не корневой каталог проектаreducerИспользовать неизменяемый JS?
    • потому что корневой каталогreducerзаключается в объединении несколькихreducerобъединены
    • будет работать очень частоreducer, и используйтеcombineRducerВозвращаемый объект используетсяimmutableУправление не может быть объединено
  • использоватьredux-immutable:
    • Установить:yarn add redux-immutable
// 根目录下src->store->reducer
import { combineReducers } from 'redux-immutable'

import { reducer as recommendReducer } from '../pages/discover/child-pages/recommend/store'

// 多个reducer合并
const cRducer = combineReducers({
  recommend: recommendReducer
})

export default cRducer
  • существуетrecommend.jsИзменить и получить в файлеstateСпособ
    • Из-за оригиналаstateдаimmutableобъект, поэтому исходный метод приобретения необходимо изменить
// 在recommend👉c-cpns👉top-banners👉index.js文件 (获取的是Immutable对象, 需要进行设置)
const { topBanners } = useSelector(state => ({
    // 下面两行获取state方式相等
    // topBanners: state.get('recommend').get('topBanners')
--> topBanners: state.getIn(['recommend', 'topBanners']) <--
  }))

Рекомендуемый баннер страницы

1. Схема зоны карусели

основной макет
  • Макет компонента карусели: TopBanner

  • Карусель используетantdВращающиеся световые компоненты↓

2. Используйте компонент antd marquee

3. Реализация размытия фона по Гауссу

  • Когда мы переключим изображение карусели на официальном сайте NetEase Cloud Music, мы обнаружим, что изображение карусели будет иметь эффект градиента при переключении.
  • Как добиться эффекта градиента: на самом деле это фоновое изображение, просто не запрашиваемое фоновое изображениеurlДобавлены дополнительные параметры

Добавьте размытие фона по Гауссу

  • На официальном сайте NetEase Cloud мы обнаружили, что фоновое изображение было добавлено только что:

    • Строка запросаquery stringПараметры ( ?imageView&blur=40x20 )

    • Просмотрите фоновое изображение (обратите внимание на область красной линии)
    • Netease облачная музыка Размытие фона по Гауссу ?imageView&blur=40x20

    • Вам нужно только передать URL-адрес фонового изображения элементу Banner и проникнуть в него через атрибут.style.jsПолученный URL-адрес для отображения (может передавать стационарный фон размытия по Гауссу для баннера)

  • Идеи реализации:

    1. Слушайте переключатель каруселиСуществует соответствующий API для компонента marquee.beforeChange, обратный вызов перед переключением панелей

    2. и использоватьuse CallbackОберните функцию события

      • Если вы не понимаете этот крючок, нажмите на меняРешено: когда родительский компонент другойstateПроизошло изменение, функция события не изменилась, но проблема была переопределена (простое резюме)
      • определить компонентcurrentIndexУказатель для записи, переходы между слайдами

    3. В параметре функции события естьfrom to

      • (toПараметр является параметром передаточной функции Carousel Carousel)
      • мы используем здесьtoпеременная как индекс для следующего переключателя
    4. согласно сcureent indexПодпись для удаления:reduxсохранено вtop BannersФоновое изображение, соответствующее массиву

    const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")
    

добиться эффекта

banner-switch

  • На данный момент мы в основном завершили базовый институциональный стиль и карусельную карту «NetEase Cloud Music».