Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/reactjs/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 在React/Redux中使用音频对象_Javascript_Reactjs_Audio_Redux - Fatal编程技术网

Javascript 在React/Redux中使用音频对象

Javascript 在React/Redux中使用音频对象,javascript,reactjs,audio,redux,Javascript,Reactjs,Audio,Redux,我目前正在开发一个音乐应用程序,在React/Redux中存储音频对象及其当前状态方面,我有一个关于如何正确处理音频对象的问题 我当前正在我的一个组件中调度一个操作,该操作将被发送到以下reducer,以将音频对象设置为状态的一部分: Reducer.js import { fromJS } from 'immutable'; const initialState = fromJS({ audioTrack: false }); export const musicPlayer = (s

我目前正在开发一个音乐应用程序,在React/Redux中存储音频对象及其当前状态方面,我有一个关于如何正确处理音频对象的问题

我当前正在我的一个组件中调度一个操作,该操作将被发送到以下reducer,以将音频对象设置为状态的一部分:

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
对象添加到reducers状态。如果在那里设置了音轨,那么我可以从reducers状态访问
audioTrack
对象,并在需要时暂停它,以及调用任何其他需要的音频方法

我这里的问题是,我非常确定将音频对象存储在一个减速机中并不是实现这种方法的正确方法。音频对象有一些深嵌套的对象,出于明显的性能原因,我希望尽可能使我的还原器保持平坦

有什么更好的方法来解决这个问题?我曾经考虑过将音频对象添加到窗口对象中,并将其状态存储在那里,但仍然不确定这是否是最合理的方法。希望将任何音频元素排除在dom之外,以防止用户执行快速检查元素并查找节点及其源


谢谢,如果有任何不清楚的地方请告诉我

不要将其存储在redux'状态

  • 整个音频对象是冗余信息(文件名/路径、播放/暂停状态、当前时间信息足够)
  • 从性能角度来看,Redux和不可变方法并不真正适合这种类型的对象
  • 而且,这种类型的对象不适合与redux开发工具/记录器/人眼一起使用
  • 您必须存储有关音频的最少信息量。因此,任何组件都可以订阅此状态。例如,实例化
    componentDidMount
    上的音频对象并将其存储在本地状态,打开
    getDerivedStateFromProps/componentWillReceiveProps
    ,销毁
    componentWillUnmount
    上的音频对象,等等


    此外,如果您想隐藏有关音频的信息,则这也是使用组件的本地状态的另一个原因。

    首先,不建议使用更改状态键类型的方法(
    audioObject
    布尔值
    更改为
    类音频
    )。您应该避免更改状态键的类型,因为它可能会导致许多无法预料的错误。人类读者在浏览代码时也很难理解它的用途

    现在,看看您的用例(一次只跟踪一个音频),我觉得在您当前的代码中,关注点的分离并不是很清楚,这就是为什么audioTrack应该保持在组件状态还是还原状态。让我们使用Redux来改进这一点

    我们将按以下方式应用关注点分离

  • 可用的操作:请参见
    actionCreator
  • 您的状态结构是什么:请参阅reducer中的
    initialState
  • 如何更新您的状态:请参阅reducer中的
    MusicLayer
  • 你的应用程序看起来是什么样子的:你的表现问题和副作用将在你的React组件中得到处理。它将被
    connect
    包装以订阅Redux状态
  • 组件如何解释Redux状态:请参阅
    MapStateTrops
  • 您的重排状态将有两个键:

  • audioTrack
    :跟踪活动音频曲目的文件名
  • isplay
    :跟踪曲目是播放还是暂停
  • 您的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
      }
    }
    
    您的MapStateTrops将如下所示:

    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
    }
    
    播放/暂停音频文件是一种副作用,应根据React 16指南在
    componentDidUpdate
    componentDidMount
    中加以注意。您可以在此处的
    注释
    部分查看:

    使用
    reselect/createSelector
    mapstatetrops
    中使用音频类包装音频文件,因为如果
    state.audioTrack
    的值没有更改,则
    createSelector
    将返回旧的音频文件。这是播放音频文件后暂停所必需的

    createSelector:

    (我在这里假设用户一次只能处理一个文件。一旦用户更改音频文件,它就可以被丢弃。如果您想维护活动音频文件的列表,可以使用生成器函数生成给定文件名的音频。)

    性能

    只要关注perf,就不必担心大型音频对象。其引用不会存储在任何位置,除非通过
    createSelector
    。当用户更改活动音频曲目时,旧曲目将在下一个GC循环中自由收集

    可扩展性

  • 添加更多属性的灵活性:如果您想添加更多属性,可以将它们存储在状态中,但请记住只存储那些实际影响组件状态的属性。对于Redux状态中的每一个更改,都将调用Reducer,并且
    MapStateTrops
    也将调用Reducer。如果不小心使用,这将减少对组件的重新渲染
  • 表示独立于状态逻辑:将来,如果您只想更改组件的表示逻辑(如使用另一个类而不是音频),则无需担心更改代码
  • 减速机是
    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