在React / Redux中使用音频对象

red*_* 87 4 javascript audio reactjs redux

我目前正在构建一个音乐应用程序,并且在将音频对象及其当前状态存储在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
  }
}
Run Code Online (Sandbox Code Playgroud)

基本上,这里如果audioTrackfalse我创建一个新的音轨当有人点击播放按钮。然后将audioTrack对象添加到reducers状态。从那里开始,如果设置了音轨,则可以audioTrack从reducers状态访问该对象,并在需要时暂停它,以及调用所需的任何其他音频方法。

我在这里的问题是,我敢肯定,将音频对象存储在reducer中不是解决这种问题的正确方法。音频对象具有一些深层嵌套的对象,出于明显的性能原因,我想使减速器尽可能平坦。

有什么更好的方法来解决这个问题?虽然我已经将音频对象添加到窗口对象,然后将其状态存储在那里,但是再次不确定这是否是最合理的方法。希望将所有音频元素都放在dom之外,以防止用户进行快速检查元素并找到该节点及其来源。

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

yes*_*hah 5

首先,不建议使用更改状态键类型(audioObjectbooleanclass Audio)的方法。您应该避免更改状态键的类型,因为它可能导致许多无法预料的错误。对于人类读者来说,在浏览代码时也很难弄清楚它的用途。

现在,看到您的用例(一次只跟踪一个音频),我觉得在您当前的代码中并没有真正明确关注点的分离,这就是为什么要在组件或还原器状态下保持audioTrack的困惑。让我们使用Redux进行改进。

我们将通过以下方式应用关注点分离。

  1. 可用的操作:请参阅 actionCreator
  2. 您的状态结构是什么:请参见initialStatereducer
  3. 您的状态如何更新:请参见musicPlayerreducer
  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,
});
Run Code Online (Sandbox Code Playgroud)

你的减速将工作如下:

//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
  }
}
Run Code Online (Sandbox Code Playgroud)

您的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
    }
}
Run Code Online (Sandbox Code Playgroud)

最后是您的演示组件

// 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
}
Run Code Online (Sandbox Code Playgroud)

播放/暂停音频文件是一个副作用所应采取的护理componentDidUpdate,并componentDidMount根据反应16个准则。您可以在以下Notes部分中进行检查:https : //reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops

使用音频类包装音频文件是在mapStateToProps使用中完成的,reselect/createSelector因为createSelector如果的值state.audioTrack未更改,它将返回较旧的音频文件。这是播放音频文件后暂停所需的。

createSelector:https : //github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc

(我在这里假设用户一次只能处理一个文件。一旦他更改了音频文件,就可以将其丢弃。万一要维护一个活动音频文件列表,可以使用生成器函数,用于在给定文件名的情况下生成音频。)

性能

只要考虑到性能,您就不必担心大型Audio对象。除以外,它的参考没有存储在任何地方createSelector。当用户更改活动音频轨道时,较旧的轨道将在下一个GC周期中免费收集。

可扩展性

  1. 添加更多属性的灵活性:如果要添加更多属性,可以将它们存储在状态中,但要记住只存储实际上影响组件状态的那些属性。对于Redux状态的每一次更改,Reducer都会被调用,您的也会被调用mapStateToProps。如果使用不当,将导致不必要的组件重新渲染。
  2. 表示独立于状态逻辑:将来,如果您只想更改组件的表示逻辑(例如使用其他类而不是Audio),则不必担心更改reducer代码。
  3. Reducer与数据的消耗方式无关:Reducer与音频文件的结构无关。它使您可以灵活地在可能要编写的另一个音频组件中使用相同的Reducer代码(例如,需要为移动应用程序编写一个单独的组件)。将您的数据和状态关注点与演示文稿关注点分开始终是一个好习惯。

我建议您观看Redux Creator Dan Abramov制作的该视频系列,以了解如何使用Redux:https//egghead.io/courses/building-react-applications-with-idiomatic-redux

我已经展示了如何使用Redux集成您的用例,因为我知道您正在尝试学习Redux。但是对于这样一个简单的应用程序,您可能不需要Redux。您可以简单地使用容器(数据/状态)和表示组件的概念。看看Dan Abramov撰写的这篇精彩文章:https : //medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0