Javascript 在React/Redux中使用音频对象
我目前正在开发一个音乐应用程序,在React/Redux中存储音频对象及其当前状态方面,我有一个关于如何正确处理音频对象的问题 我当前正在我的一个组件中调度一个操作,该操作将被发送到以下reducer,以将音频对象设置为状态的一部分: Reducer.jsJavascript 在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
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'状态
componentDidMount
上的音频对象并将其存储在本地状态,打开getDerivedStateFromProps/componentWillReceiveProps
,销毁componentWillUnmount
上的音频对象,等等
此外,如果您想隐藏有关音频的信息,则这也是使用组件的本地状态的另一个原因。首先,不建议使用更改状态键类型的方法(
audioObject
从布尔值
更改为类音频
)。您应该避免更改状态键的类型,因为它可能会导致许多无法预料的错误。人类读者在浏览代码时也很难理解它的用途
现在,看看您的用例(一次只跟踪一个音频),我觉得在您当前的代码中,关注点的分离并不是很清楚,这就是为什么audioTrack应该保持在组件状态还是还原状态。让我们使用Redux来改进这一点
我们将按以下方式应用关注点分离
actionCreator
initialState
MusicLayer
connect
包装以订阅Redux状态MapStateTrops
audioTrack
:跟踪活动音频曲目的文件名isplay
:跟踪曲目是播放还是暂停// 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