我正在 React 中构建一个音乐播放器,当道具
title
和 artist
发生变化时,我尝试使用 useEffect
动态更新曲目 title
和 artist
。然而,虽然值在 console.log()
中正确更新,但它们不会反映在 DOM 中。
我正在为
useState
和 title
使用状态钩子 artist
,并且我确保当这些状态发生变化时组件应该重新渲染。尽管如此,DOM 仍然不会更新为新值。
import React, { useEffect, useState } from 'react';
import { FaBackward, FaPlay, FaPause, FaForward, FaHeart, FaRandom, FaVolumeUp } from 'react-icons/fa';
import useAudio from '../Audio/useAudio';
import api from '../../Services/api';
const Playbar = ({ url, title, artist, id }) => {
//url is the audio url
//title is the title of the track
//artist is the artist of the song
//id is the id of the track
const [playing, toggle] = useAudio(url);
const [titles, setTitles] = useState(title || "");
const [artists, setArtists] = useState(artist || "");
// Update titles and artists when props change
useEffect(() => {
setTitles(title || "");
setArtists(artist || "");
}, [title, artist]);
// Update current playing track in the database if playing is true and the track has changed
useEffect(() => {
if (playing) {
api.post(`/api/currentplaying`, {
playing: playing,
title: titles,
artist: artists,
id: id
});
}
}, [titles, artists, playing, id]);
// Fetch current playing track from the database
useEffect(() => {
const fetchCurrentPlaying = async () => {
try {
const res = await api.get(`/api/fetchcurrentplaying/${id}`);
if (res.data.current && res.data.current.length > 0) {
const currentTrack = res.data.current[0];
setTitles(currentTrack.title);
setArtists(currentTrack.artist);
} else {
console.log("No current playing data found.");
}
} catch (error) {
console.error('Error fetching current playing:', error);
}
};
fetchCurrentPlaying();
}, [id]);
// Log when the playbar is updated
useEffect(() => {
console.log("Playbar updated with:", { url, title, artist });
}, [url, title, artist]);
return (
<footer className="fixed text-white bottom-0 left-0 right-0 bg-gray-800 p-2 flex items-center">
<div className="flex items-center">
<img src="https://placehold.co/50x50" alt="Current song" className="rounded-full mr-4" />
<div>
<p className="font-bold">{titles || "No Title"}</p>
{console.log("Title", titles)}
{console.log("Artist", artists)}
<p>{artists || "No Artist"}</p>
</div>
</div>
<div className="flex-1 flex justify-center items-center">
<FaBackward className="mx-4" />
{playing ? (
<FaPause className="mx-4" onClick={toggle} />
) : (
<FaPlay className="mx-4 " onClick={toggle} />
)}
<FaForward className="mx-4" />
</div>
<div className="flex items-center">
<FaHeart className="mx-4" />
<FaRandom className="mx-4" />
<FaVolumeUp className="mx-4" />
</div>
</footer>
);
}
export default Playbar;
问题:
title
和 artist
属性被传递到 Playbar 组件中,并且状态变量标题和艺术家在 useEffect
内相应更新。
这些状态变量正确记录在 console.log()
中,但 DOM 未反映更新后的状态值。
我已经确认状态正在控制台中更新,但它没有按预期重新渲染 DOM。
我尝试过的:
我使用
useEffect
在 props 更改时更新状态值。
我确保正确调用 setTitles
和 setArtists
。
我在 Playbar 组件中使用 React.memo,但问题仍然存在。
任何人都可以帮助我确定为什么更新的状态值没有反映在 DOM 中,即使它们已正确记录?
使用Audio.js
import { useState, useEffect } from 'react';
const useAudio = (url) => {
const [audio, setAudio] = useState(new Audio(url));
const [playing, setPlaying] = useState(false);
const toggle = () => {
setPlaying(prev => !prev);
};
useEffect(() => {
const handleEnded = () => setPlaying(false);
audio.addEventListener('ended', handleEnded);
return () => {
audio.pause();
audio.removeEventListener('ended', handleEnded);
};
}, [audio]);
useEffect(() => {
if (url && url !== undefined) {
const newAudio = new Audio(url);
setAudio(newAudio);
audio.src = url;
setPlaying(true);
}else{
setPlaying(false);
}
}, [url]);
useEffect(() => {
if (playing && url) {
audio.play().catch(error => {
console.error("Error playing audio:", error);
});
} else {
audio.pause();
}
}, [playing, audio]);
return [playing, toggle];
};
export default useAudio;
请查看以下代码。
...
useEffect(() => {
setTitles(title || "");
setArtists(artist || "");
}, [title, artist]);
useEffect(() => {
if (playing) {
api.post(`/api/currentplaying`, {
playing: playing,
title: titles,
artist: artists,
id: id
});
}
}, [titles, artists, playing, id]);
...
可能出现的故障
如果每当标题或艺术家更改时 id 或playing 也发生更改,则第二个 useEffect 挂钩将更新数据库中的过时的标题或状态艺术家或两者。
失败原因
失败的原因是,React 将所有 useEffect 句柄合并为一个 useEffect 并执行它。代码的顺序将相对于源代码。每个处理程序的适用性将与依赖项数组相关。
因此,如果每当标题或艺术家改变时,playing 或 id 也改变,那么最终的 useEffect 将如下所示。 它是单个 useEffect 处理程序。当它执行时,状态设置器 setTitles 或 setArtists 会将状态更改排队并触发新的渲染。然而,正如我们所知,只有当当前运行的处理程序完全结束时,这个新的渲染才会开始。因此 React 也会继续运行 post 查询。由于处理程序中标题和艺术家的状态值始终保留为上次渲染的快照,它是陈旧的。因此,新值将意外地不会更新到数据库中。
...
useEffect(() => {
setTitles(title || "");
setArtists(artist || "");
if (playing) {
api.post(`/api/currentplaying`, {
playing: playing,
title: titles,
artist: artists,
id: id
});
}, [title, artist, playing, id]);
...
一个可能的解决方案
根据组件中收到的相应道具,可能不需要以下两个状态定义。因此,不需要的 props 可以被删除,并直接引用相应的 props 来代替它。此更改也将消除通过 useEffect 重置的需要。您也许可以阅读这里提倡的这种代码重构根据 props 或 state 更新状态
const [titles, setTitles] = useState(title || "");
const [artists, setArtists] = useState(artist || "");
另一种解决方案
如果你想保留状态,也可以。但是,请使用按键而不是 useEffect 重置状态。使用密钥可能是确保正确重置相关状态的最佳方法。此处已对此进行了详细介绍当道具更改时重置所有状态。