предисловие
В этой статье начинается проект "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
данные украшают
-
Redux DevTools: легко отлаживать
Предисловие - предварительный просмотр проекта и исходный код
-
Адрес онлайн-просмотра 👉: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
компоненты
- Помещение: в
-
Установить
router
:yarn add react-router-dom
-
Установить
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;
-
существует
App.js
использоватьHashRouter
Использование пакета компонентовrouter-config
Настроенная карта маршрутов (чтобы конфигурация таблицы карты маршрутов вступила в силу):-
Нажмите, чтобы просмотреть конфигурацию в App.js
-
Убедитесь, что маршрут настроен успешно:существует
header
компонент, использованиеNavLink
Тестовое переключение путей и рендеринг соответствующих компонентов -
Нажмите, чтобы просмотретьполный эффект
-
Завершенный эффект выглядит следующим образом👇
- основное содержание следовать
URL
Происходят изменения, обратите внимание на изменения путей и переключения компонентов
Компонент заголовка заголовка
1. Написание стиля компонента заголовка
- дляПредотвращение конфликтов стилей в нескольких компонентах, использовать стили внутри компонента
styled-components
библиотека - Установить с помощью:
yarn add styled-components
- Использование макета:
Flex
2. Отделение области головы
3. Реализация и идеи зоны головы (слева)
实现功能:点击头部列表项,添加背景实现高亮和下面的小三角
实现思路:(利用`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. Документация по интерфейсу проекта
- Документация по интерфейсу API (опционально 1): локальная установка и развертывание
- Установка документации интерфейса API
- использование: местное
Node
сервер работает - API-сервер работает
- Интерфейс, используемый проектом (опционально 2):http://123.57.176.198:3000
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, нажмите для просмотра
-
-
Теперь начнем запрашивать данные карусели:
- Карусельный API: /banner
- Демо:http://123.57.176.198:3000/banner
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>
)
})
резюме
- Создайте
combinReducers
- используется для объединения нескольких
reducer
сливаться
- используется для объединения нескольких
- Создайте
store
- настроить промежуточное ПО и
redux-devtools
- настроить промежуточное ПО и
- настроить
react-redux
- Помогите нам подключиться
redux
- Помогите нам подключиться
3. Данные карусели запрашиваются через redux-thunk
-
Интерфейс API карусельных данных:
- /banner
- Демо 👉:http://123.57.176.198:3000/banner
- Поместите запрошенные данные карусели в
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
- Для получения подробной информации см.:оптимизация производительности 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
-
Используйте combReducers в redux-immutable;
-
Все данные в редукторе преобразуются в неизменяемые данные типа
Immutablejs Интеграционный проект
Используйте ImmutableJS для редуктора текущего каталога проекта.
// 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
-
использовать
useRef
Получите метод переключения карусельных изображений, предоставляемых компонентом выделения:prev() next()
- Используйте две кнопки управления для переключения следующего изображенияAnta-design.git ee.IO/components/…
3. Реализация размытия фона по Гауссу
- Когда мы переключим изображение карусели на официальном сайте NetEase Cloud Music, мы обнаружим, что изображение карусели будет иметь эффект градиента при переключении.
- Как добиться эффекта градиента: на самом деле это фоновое изображение, просто не запрашиваемое фоновое изображение
url
Добавлены дополнительные параметры
Добавьте размытие фона по Гауссу
-
На официальном сайте NetEase Cloud мы обнаружили, что фоновое изображение было добавлено только что:
-
Строка запроса
query string
Параметры ( ?imageView&blur=40x20 ) -
Просмотрите фоновое изображение (обратите внимание на область красной линии)
-
Netease облачная музыка Размытие фона по Гауссу
?imageView&blur=40x20
-
Вам нужно только передать URL-адрес фонового изображения элементу Banner и проникнуть в него через атрибут.
style.js
Полученный URL-адрес для отображения (может передавать стационарный фон размытия по Гауссу для баннера)
-
-
Идеи реализации:
-
Слушайте переключатель каруселиСуществует соответствующий API для компонента marquee.
beforeChange
, обратный вызов перед переключением панелей -
и использовать
use Callback
Оберните функцию события-
Если вы не понимаете этот крючок, нажмите на меня
Решено: когда родительский компонент другойstate
Произошло изменение, функция события не изменилась, но проблема была переопределена (простое резюме) -
определить компонент
currentIndex
Указатель для записи, переходы между слайдами
-
-
В параметре функции события есть
from to
- (
to
Параметр является параметром передаточной функции Carousel Carousel) - мы используем здесь
to
переменная как индекс для следующего переключателя
- (
-
согласно с
cureent index
Подпись для удаления:redux
сохранено вtop Banners
Фоновое изображение, соответствующее массиву
const bgImage = topBanners[currentIndex] && (topBanners[currentIndex].imageUrl + "?imageView&blur=40x20")
-
добиться эффекта
- На данный момент мы в основном завершили базовый институциональный стиль и карусельную карту «NetEase Cloud Music».