Работа с аудиообъектами в React / Redux - PullRequest
0 голосов
/ 07 июня 2018

В настоящее время я создаю музыкальное приложение и у меня есть вопрос о том, как правильно подходить к звуковым объектам с точки зрения их хранения и их текущего состояния в React / Redux.

В настоящее время я отправляю действие в один из моих компонентов, который использует следующий редуктор для установки звукового объекта как части состояния:

Reducer.js

import { fromJS } from 'immutable';

const initialState = fromJS({
  audioTrack: false
});

export const musicPlayer = (state = initialState, action) => {
  switch (action.type) {
    case 'musicPlayer/PLAY_TRACK': {
      const mergeObj = {};
      const audioTrack = state.get('audioTrack');
      mergeObj.audioTrack = audioTrack;
      if (!audioTrack) {
        mergeObj.audioTrack = new Audio('../../public/music/test.mp3');
        mergeObj.audioTrack.play();
      } else if (state.get('audioTrack').paused) {
        mergeObj.audioTrack.play();
      } else {
        mergeObj.audioTrack.pause();
      }
      return state.merge(mergeObj);
    }
    default: return state
  }
}

В основном здесь, если audioTrack равно false Я создаю новую звуковую дорожку, когда кто-то нажимает кнопку воспроизведения.Затем я добавляю объект audioTrack в состояние редукторов.Оттуда, если дорожка установлена, я могу получить доступ к объекту audioTrack из состояния редукторов и приостановить его, если мне нужно, а также вызвать любые другие аудио методы, которые мне нужны.

Моя проблема здесь в том, что я уверен, что хранение аудиообъекта в редукторе не является правильным способом приблизиться к чему-то подобному.Аудиообъект имеет несколько глубоко вложенных объектов, и я хотел бы сохранить мои редукторы как можно более плоскими по очевидным причинам производительности.

Что может быть лучше для этого?Хотя я добавил аудиообъект к объекту окна и просто сохранил его состояние там, но опять же не уверен, что это наиболее разумный подход.Хотелось бы, чтобы любые аудио элементы отсутствовали в домене, чтобы пользователи не могли быстро проверить элемент и найти узел вместе с его источником.

Спасибо и, пожалуйста, дайте мне знать, если что-то неясно!

Ответы [ 3 ]

0 голосов
/ 15 июня 2018

Во-первых, подход к изменению типа ключа состояния (audioObject с boolean на class Audio) не рекомендуется.Вы должны воздерживаться от изменения типа ключа состояния, так как это может привести ко многим непредвиденным ошибкам.Читателю-человеку также трудно понять, как его использовать, просматривая код.

Теперь, рассматривая ваш вариант использования (отслеживая только один звук за раз), я чувствую, что разделениеВ вашем текущем коде не совсем ясно, из-за чего возникает путаница, следует ли поддерживать audioTrack в состоянии компонента или редуктора.Давайте улучшим это, используя Redux.

Мы применим разделение проблем следующим образом.

  1. Какие действия доступны: См. actionCreator
  2. Какова ваша структура состояния: См. initialState в редукторе
  3. Как обновляется ваше состояние: См. musicPlayer в редукторе
  4. Как выглядит ваше приложение: Ваши проблемы с презентацией и побочные эффекты будут учтены в вашем компоненте React.Он будет заключен в connect для подписки на состояние Redux.
  5. Как ваш компонент интерпретирует состояние Redux ?: См. mapStateToProps

Ваше Состояние Redux будет иметь 2 клавиши:

  1. audioTrack: отслеживает имя файла активной звуковой дорожки.
  2. 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.

Масштабируемость

  1. Гибкость добавления дополнительных атрибутов : если вы хотите добавить больше атрибутов, вы можете сохранить их в состоянии, ноне забудьте хранить только те атрибуты, которые действительно влияют на состояние вашего компонента.Для каждого изменения в вашем состоянии Redux будет вызываться ваш Reducer, а также ваш mapStateToProps.Это приведет к бесполезному повторному рендерингу ваших компонентов, если не использовать его осторожно.
  2. Презентация не зависит от логики состояния : В будущем, если вы захотите изменить только логику представлениявашего компонента (например, использование другого класса вместо Audio), вам не нужно беспокоиться об изменении кода редуктора.
  3. Редуктор не зависит от способа использования данных. : Редуктор не зависит от структуры аудиофайла.Это дает вам гибкость в использовании того же кода 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

0 голосов
/ 28 августа 2018

У меня была такая же проблема, и вот как я это сделал: сначала создайте аудио компонент примерно так

import CSSModules from 'react-css-modules'
import React, { PureComponent } from 'react'
import styles from '../../app.sass'
import isUndefined from 'lodash/isUndefined'
import {connect} from "react-redux"
import {setActiveTrack} from "../../containers/Audio/actions";


class Audio extends PureComponent {
  constructor (props) {
    super(props)
    const { src, controlsList, preload, controls, autoPlay } = props
    this.state = {
      src: src,
      controlsList: isUndefined(controlsList) ? 'nodownload' : controlsList,
      preload: isUndefined(preload) ? 'none' : preload,
      controls: isUndefined(controls) ? true : controls,
      autoPlay: isUndefined(autoPlay) ? false : autoPlay,
    }
  }

  setActiveTrackHandler = () => {
    const { dispatch, uid } =this.props
    dispatch(setActiveTrack(uid))
  }


  render () {
    const {uid, audioTrack} =this.props
    if (this.audioRef && uid !== audioTrack) {
      this.audioRef.pause()
    }
    return (
      <audio
        ref={audio => this.audioRef = audio}
        controls={this.state.controls}
        preload={this.state.preload}
        autoPlay={this.state.autoPlay}
        style={{ width: '100%' }}
        controlsList={this.state.controlsList}
        onPlay={this.setActiveTrackHandler}
      >
        <source src={this.state.src} />
      </audio>
    )
  }

}


function mapStateToProps (state){
  return {
    audioTrack: state.audioReducer.audioTrack
  }
}

const AudioClass = CSSModules(Audio,styles, { allowMultiple: true })
export default connect(mapStateToProps)(AudioClass)

Тогда вот ваши константы

export const SET_ACTIVE_TRACK = 'containers/Audio/SET_ACTIVE_TRACK'

А вот вашаДействия

import {
  SET_ACTIVE_TRACK,
  PLAY_TRACK
} from './constants'

// sets active track
export function setActiveTrack (activeTrack) {
  return {
    type: SET_ACTIVE_TRACK,
    payload: activeTrack
  }

}

и вот редуктор

import {
  SET_ACTIVE_TRACK
} from './constants'



const initialState = {
  audioTrack: null,
  isPlaying: false,
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {

  case SET_ACTIVE_TRACK:
    return {
      ...state,
      audioTrack: action.payload,
    }

  default: return state
  }
}

export default reducer
0 голосов
/ 12 июня 2018

Не сохранять его в состоянии избыточности.

  1. Весь аудиообъект избыточен информация (имя / путь к файлу, состояние воспроизведения / паузы, информация о текущем временидостаточно).
  2. Redux и неизменный подход не очень подходят для этого типа объектов с точки зрения производительности.
  3. И этот тип объектов не подходит для работы с инструментами redux dev/ logger / human eyes.

Вы должны хранить минимальное количество информации об аудио.Таким образом, любой компонент может подписаться на это состояние.Например, создайте экземпляр аудиообъекта на componentDidMount и сохраните его в локальном состоянии, включите аудио на getDerivedStateFromProps/componentWillReceiveProps, уничтожьте аудиообъект на componentWillUnmount и т. Д.

Также, если вы хотите скрыть информациюЧто касается аудио, то это еще одна причина использовать локальное состояние компонента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...