React Context引发列表全重渲染及状态持久化问题求助
问题根源
你现在的核心问题是:全局状态(Context/Redux)里用了单一的布尔值isPlaying,所有MainSound组件都共享这个状态,所以点击任意列表项时,所有项都会同步切换播放状态。而本地useState是每个组件独立维护状态,所以没问题,但路由切换后状态丢失——因此需要把全局状态从「是否播放」改成「当前正在播放的音频唯一标识」,同时做持久化。
解决方案步骤
1. 修改PlayingContext的状态结构
把原来单一的isPlaying布尔值,改成记录当前播放音频的唯一ID,同时加入localStorage持久化:
// playing-context.jsx import { createContext, useState, useEffect, useRef } from 'react'; const PlayingContext = createContext({ currentPlayingId: null, togglePlay: () => {} }); export const PlayingProvider = ({ children }) => { // 从localStorage读取持久化的播放状态 const [currentPlayingId, setCurrentPlayingId] = useState(() => { return localStorage.getItem('currentPlayingId') || null; }); // 全局音频实例,确保同一时间只有一个音频播放 const audioRef = useRef(null); // 播放/切换音频的逻辑 const togglePlay = (id, audioSource) => { if (currentPlayingId === id) { // 暂停当前音频 audioRef.current?.pause(); setCurrentPlayingId(null); } else { // 停止之前的音频,播放新音频 if (audioRef.current) audioRef.current.pause(); audioRef.current = new Audio(audioSource); audioRef.current.play(); setCurrentPlayingId(id); } }; // 状态更新时同步到localStorage useEffect(() => { localStorage.setItem('currentPlayingId', currentPlayingId); }, [currentPlayingId]); return ( <PlayingContext.Provider value={{ currentPlayingId, togglePlay }}> {children} </PlayingContext.Provider> ); }; export default PlayingContext;
2. 修正MainSoundList的代码
- 获取Firestore数据时保留文档唯一ID(每个音频的标识)
- 列表项
key改用音频ID(不要用Math.random(),避免React渲染错误) - 点击事件调用Context的
togglePlay方法:
// MainSoundList.jsx import React, { useState, useEffect, useContext } from 'react'; import MainSound from './MainSound'; import MainSoundSkeleton from './MainSoundSkeleton'; import classes from './MainSoundList.module.scss'; import { db } from '../../../../config/firebase-config'; import { getDocs, collection } from 'firebase/firestore'; import PlayingContext from '../../../../context/playing-context'; const MainSoundList = () => { const [sounds, setSounds] = useState([]); const [isLoading, setIsLoading] = useState(false); const { togglePlay } = useContext(PlayingContext); useEffect(() => { const getSounds = async () => { try { setIsLoading(true); const data = await getDocs(collection(db, 'sounds')); // 保留每个文档的ID作为音频唯一标识 const sounds = data.docs.map((doc) => ({ id: doc.id, ...doc.data() })); setSounds(sounds); } catch (error) { console.log(error); } finally { setIsLoading(false); } }; getSounds(); }, []); const soundsList = sounds.map((sound) => ( <MainSound key={sound.id} id={sound.id} title={sound.title} imageSource={sound.imageSource} audioSource={sound.audioSource} onClick={() => togglePlay(sound.id, sound.audioSource)} /> )); return ( <> {isLoading ? ( <MainSoundSkeleton /> ) : ( <ul className={classes['main__sounds']}>{soundsList}</ul> )} </> ); }; export default MainSoundList;
3. 修改MainSound组件
不再依赖全局布尔值,通过自身ID和currentPlayingId对比判断播放状态:
// MainSound.jsx import React, { useContext } from 'react'; import classes from './MainSound.module.scss'; import svg from '../../../../assets/svg/sprite.svg'; import PlayingContext from '../../../../context/playing-context'; const MainSound = ({ id, imageSource, title, audioSource, onClick }) => { const { currentPlayingId } = useContext(PlayingContext); // 自身ID等于全局记录的ID,说明正在播放 const isPlaying = currentPlayingId === id; const playClickHandler = () => { onClick(); }; return ( <li className={classes['main__sound']}> <div className={` ${classes.box} ${isPlaying ? classes.isPlaying : ''} `}> <img className={classes['box__img']} src={imageSource} alt={title} /> <button onClick={playClickHandler} className={classes.box__playicon}> <svg> <use xlinkHref={`${svg}#icon-${isPlaying ? 'pause' : 'play'}`}></use> </svg> </button> <button className={classes.box__hearticon}> <svg> <use xlinkHref={`${svg}#icon-heart`}></use> </svg> </button> <div className={`${classes.box__menu} ${isPlaying ? '' : classes.hidden}`}> <div className={classes.box__icons}> <svg className={classes['icon__volume']}> <use xlinkHref={`${svg}#icon-volume-2`}></use> </svg> </div> </div> </div> <div className={classes['main__text']}> <h3 className={classes['main__title']}>{title}</h3> <p className={classes['main__author']}>by Valdemaras</p> </div> </li> ); }; export default MainSound;
为什么Redux也出同样问题?
因为你在Redux里也用了单一的布尔值isPlaying,和Context的逻辑完全一致,改成记录「当前播放音频ID」的结构就能解决。
内容的提问来源于stack exchange,提问作者xSls0n_
相关产品推荐
相关产品推荐

