简单的react解决方案
为了使音频播放器与应用程序的其余部分保持同步,您可以使用经典的 React props down events up data flow。这意味着您将状态保留在父组件中,并将其作为props以及修改状态的事件处理程序传递给子组件。更具体地说,在您所在的州,您可以拥有:
seekTime
:这将用于强制更新您的播放器的时间
appTime
:这将由您的播放器广播并传递给其他组件,以使它们与播放器保持同步。
示例实现可能如下所示:
import { useRef, useEffect, useState } from "react";
function App() {
// we'll define state in parent component:
const [playing, setPlaying] = useState(false);
const [duration, setDuration] = useState(0);
const [seekTime, setSeekTime] = useState(0); // forces player to update its time
const [appTime, setAppTime] = useState(0); // dictated by player, broadcasted to other components
// state will be passed as props and setter function will allow child components to change state:
return (
<div>
<button onClick={() => setPlaying(true)}>PLAY</button>
<button onClick={() => setPlaying(false)}>PAUSE</button>
<button onClick={() => setSeekTime(appTime - 5)}>-5 SEC</button>
<button onClick={() => setSeekTime(appTime + 5)}>+5 SEC</button>
<Seekbar
value={appTime}
min="0"
max={duration}
onInput={(event) => setSeekTime(event.target.value)}
/>
<Player
playing={playing}
seekTime={seekTime}
onTimeUpdate={(event) => setAppTime(event.target.currentTime)}
onLoadedData={(event) => setDuration(event.target.duration)}
/>
</div>
);
}
function Seekbar({ value, min, max, onInput }) {
return (
<input
type="range"
step="any"
value={value}
min={min}
max={max}
onInput={onInput}
/>
);
}
function Player({ playing, seekTime, onTimeUpdate, onLoadedData }) {
const ref = useRef(null);
if (ref.current) playing ? ref.current.play() : ref.current.pause();
//updates audio element only on seekTime change (and not on each rerender):
useEffect(() => (ref.current.currentTime = seekTime), [seekTime]);
return (
<audio
src="./your.file"
ref={ref}
onTimeUpdate={onTimeUpdate}
onLoadedData={onLoadedData}
/>
);
}
export default App;
类似 Redux 的解决方案
如果您更喜欢类似 redux 的解决方案,您可以将应用程序的状态移动到 reducer 函数并像这样重写父组件:
import { useRef, useEffect, useReducer } from "react";
// define reducer and initial state outside of component
const initialState = { playing: false, duration: 0, seekTime: 0, appTime: 0 };
function reducer(state, action) {
switch (action.type) {
case "play":
return { ...state, playing: true };
case "pause":
return { ...state, playing: false };
case "set-duration":
return { ...state, duration: action.value };
case "set-time":
return { ...state, appTime: action.value };
case "seek":
return { ...state, seekTime: action.value };
default:
throw new Error("Unhandled action " + action.type);
}
}
function App() {
// use reducer and dispatch instead of state
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatch({ type: "play" })}>PLAY</button>
<button onClick={() => dispatch({ type: "pause" })}>PAUSE</button>
<button
onClick={() => dispatch({ type: "seek", value: state.appTime + 5 })}
>
-5 SEC
</button>
<button
onClick={() => dispatch({ type: "seek", value: state.appTime + 5 })}
>
+5 SEC
</button>
<Seekbar
value={state.appTime}
min="0"
max={state.duration}
onInput={(event) =>
dispatch({ type: "seek", value: event.target.value })
}
/>
<Player
playing={state.playing}
seekTime={state.seekTime}
onTimeUpdate={(event) =>
dispatch({ type: "set-time", value: event.target.currentTime })
}
onLoadedData={(event) =>
dispatch({ type: "set-duration", value: event.target.duration })
}
/>
</div>
);
}
// The rest of app doesn't need any change compared to previous example.
// That's due to decoupled architecture!
function Seekbar({ value, min, max, onInput }) {
return (
<input
type="range"
step="any"
value={value}
min={min}
max={max}
onInput={onInput}
/>
);
}
function Player({ playing, seekTime, onTimeUpdate, onLoadedData }) {
const ref = useRef(null);
if (ref.current) playing ? ref.current.play() : ref.current.pause();
useEffect(() => (ref.current.currentTime = seekTime), [seekTime]);
return (
<audio
src="./your.file"
ref={ref}
onTimeUpdate={onTimeUpdate}
onLoadedData={onLoadedData}
/>
);
}
export default App;
在应用程序周围传递 DOM 引用?
简单地将 ref 传递给应用程序周围的 DOM 音频元素,而不是实现适当的状态管理,这可能很诱人。但是,此解决方案会将您的组件耦合在一起,从而使您的应用程序更难维护。因此,除非您真的真的需要 React 的虚拟 DOM 花费的 3 毫秒(并且在大多数情况下您不需要),否则我建议不要这样做。