Во-первых, подход к изменению типа ключа состояния (audioObject
с boolean
на class Audio
) не рекомендуется.Вы должны воздерживаться от изменения типа ключа состояния, так как это может привести ко многим непредвиденным ошибкам.Читателю-человеку также трудно понять, как его использовать, просматривая код.
Теперь, рассматривая ваш вариант использования (отслеживая только один звук за раз), я чувствую, что разделениеВ вашем текущем коде не совсем ясно, из-за чего возникает путаница, следует ли поддерживать audioTrack в состоянии компонента или редуктора.Давайте улучшим это, используя Redux.
Мы применим разделение проблем следующим образом.
- Какие действия доступны: См.
actionCreator
- Какова ваша структура состояния: См.
initialState
в редукторе - Как обновляется ваше состояние: См.
musicPlayer
в редукторе - Как выглядит ваше приложение: Ваши проблемы с презентацией и побочные эффекты будут учтены в вашем компоненте React.Он будет заключен в
connect
для подписки на состояние Redux. - Как ваш компонент интерпретирует состояние Redux ?: См.
mapStateToProps
Ваше Состояние Redux будет иметь 2 клавиши:
audioTrack
: отслеживает имя файла активной звуковой дорожки. isPlaying
: отслеживает, воспроизводится или приостановлена дорожка.
Ваш actionCreator будет выглядеть следующим образом:
// sets active track
export const setActiveTrack (activeTrack) => ({
type: 'musicPlayer/SET_ACTIVE_TRACK',
payload: activeTrack,
});
// plays active track
export const playTrack () => ({
type: 'musicPlayer/PLAY_TRACK',
payload: true,
});
// pauses active track
export const pauseTrack () => ({
type: 'musicPlayer/PLAY_TRACK',
payload: false,
});
Ваш редуктор будет работать следующим образом:
//always use null as an indicator of empty.
const initialState = {
audioTrack: null,
isPlaying: false,
};
export const musicPlayer = (state = initialState, action) => {
switch (action.type) {
case 'musicPlayer/SET_ACTIVE_TRACK':
return {
...state,
audioTrack: action.payload,
}
case 'musicPlayer/PLAY_TRACK':
return {
...state,
isPlaying: action.payload,
}
default: return state
}
}
Ваш mapStateToProps будет выглядеть следующим образом:
mapStateToProps(state) {
return {
audioTrack: createSelector(state.audioTrack, audioTrack => new Audio(audioTrack)), // `createSelector` will return the same old Audio instance unless the `audioTrack` value changes
isPlaying: state.isPlaying
}
}
Наконец, ваш презентационный компонент :
// Take care of side-effects in componentDidMount (for first render) or componentDidUpdate (for all other renders)
componentDidUpdate() {
if(this.props.audioTrack) {
const audioTrack = this.props.audioTrack;
if(this.props.isPlaying) audioTrack.play();
else audioTrack.pause();
}
}
render() {
return ...; // your presentation logic
}
Воспроизведение / приостановка аудио файлаявляется побочным эффектом, о котором следует позаботиться в componentDidUpdate
и componentDidMount
в соответствии с рекомендациями Реакта 16.Вы можете проверить это в разделе Notes
здесь: https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops
Обтекание аудиофайла с помощью Audio Class выполняется в mapStateToProps
с использованием reselect/createSelector
, потому что createSelector
вернет старый аудиофайлесли значение state.audioTrack
не изменилось.Это необходимо для приостановки воспроизведения аудиофайла.
createSelector: https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc
(Я предполагал, что пользователь будет работать только с одним файлом за раз. Как только он изменяет аудиофайл, он может бытьв случае, если вы хотите сохранить список активных аудиофайлов, вы можете использовать функцию генератора, которая генерирует звук по имени файла.)
Производительность
Что касается перфорации, вам не нужно беспокоиться о большом объекте Audio.Его ссылка не сохраняется нигде, кроме как createSelector
.Когда пользователь изменяет активную звуковую дорожку, более старая дорожка будет свободна для сбора в следующем цикле GC.
Масштабируемость
- Гибкость добавления дополнительных атрибутов : если вы хотите добавить больше атрибутов, вы можете сохранить их в состоянии, ноне забудьте хранить только те атрибуты, которые действительно влияют на состояние вашего компонента.Для каждого изменения в вашем состоянии Redux будет вызываться ваш Reducer, а также ваш
mapStateToProps
.Это приведет к бесполезному повторному рендерингу ваших компонентов, если не использовать его осторожно. - Презентация не зависит от логики состояния : В будущем, если вы захотите изменить только логику представлениявашего компонента (например, использование другого класса вместо Audio), вам не нужно беспокоиться об изменении кода редуктора.
- Редуктор не зависит от способа использования данных. : Редуктор не зависит от структуры аудиофайла.Это дает вам гибкость в использовании того же кода Reducer в другом аудио-компоненте, который вы можете написать (например, вам нужно написать отдельный компонент для мобильных приложений).Это всегда хорошая практика, чтобы отделить ваши данные и проблемы состояния от ваших проблем презентации.
Я порекомендую проверить эту серию видео от Redux Creator Дана Абрамова, чтобы понять, как использовать Redux: https://egghead.io/courses/building-react-applications-with-idiomatic-redux
Я продемонстрировал, как интегрировать ваш сценарий использования с помощью Redux, так как я понял, что вы пытаетесь изучать Redux.Но для такого простого приложения вам может не понадобиться Redux.Вы можете просто использовать концепцию Контейнер (данные / состояние) и презентационные компоненты.Проверьте эту удивительную статью Дана Абрамова: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0