Mobx如何实现仅在computed值变化时才重新渲染对应列表项?
问题根因
- 首次全量重渲染的原因:你在每个列表项组件内部直接读取了
store.playingVideo这个可观察属性,播放切换时playingVideo返回值变更,会触发所有依赖它的组件重渲染。你在渲染函数内临时创建computed并调用get()的写法没有缓存效果,每次渲染都会新建computed实例,无法避免依赖收集。 - 后续列表不更新的原因:大概率是
store.player或store.player.videoId没有被标记为可观察属性,Mobx无法监听到值的变更,所以observer包裹的Playlist组件不会触发重渲染。
解决方案
方案一:使用createTransformer实现单组件精准更新
Mobx的createTransformer可以缓存每个id对应的激活状态计算结果,只有当计算结果发生变化时才会触发对应组件更新,其余组件完全不会重渲染。
import { makeAutoObservable, createTransformer } from "mobx"; import { observer } from "mobx-react-lite"; // 首先确认store所有属性已标记为可观察 class AppStore { player = { videoId: null, isStopped: true }; videos = {}; playlist = []; constructor() { makeAutoObservable(this); // 必不可少,让Mobx监听属性变化 } get playingVideo() { if (!this.player.videoId || this.player.isStopped) return null; return this.videos[this.player.videoId]; } } // 创建转换器,缓存每个视频id对应的激活状态 const getIsMediaActive = createTransformer((videoId, store) => { return store.player.videoId === videoId && !store.player.isStopped; }); const DraggableMediaItem = observer(({ video, index }) => { const store = useAppStore(); // 仅当前视频的激活状态变化时,才会触发组件重渲染 const isMediaActive = getIsMediaActive(video.id, store); console.log("RENDER", video.id); // 仅新旧两个激活项会打印日志 const onMediaClicked = () => { if (!isMediaActive) { playerAPI.playMedia(video.id).catch(snackBarHandler(store)); return; } playerAPI.pauseMedia().catch(snackBarHandler(store)); }; // 剩余渲染逻辑保持不变 let activeMediaProps = {}; if (isMediaActive) { activeMediaProps = { autoFocus: true, sx: { backgroundColor: "rgba(246,250,254,1)" }, }; } return ( <Draggable draggableId={video.id} index={index}> {/* 原有渲染逻辑 */} </Draggable> ); });
方案二:父组件传参配合React.memo优化
如果采用父组件传递isActive参数的写法,只需要补全可观察标记和缓存逻辑即可:
const Playlist = observer(() => { const store = useAppStore(); const items = store.playlist; const activeId = store.player.videoId; // 提前读取可观察值,触发依赖收集 return ( <List> {items.map((item) => ( <MediaItem key={item.id} // React列表必须加唯一key video={item} isActive={item.id === activeId} /> ))} </List> ); }); // 用React.memo包裹,仅props变化时才重渲染组件 const MediaItem = React.memo(observer(({ video, isActive }) => { let activeMediaProps = {}; if (isActive) { activeMediaProps = { sx: { backgroundColor: "rgba(246,250,254,1)" }, }; } return <ListItem {...activeMediaProps}> ... </ListItem>; }));
两种方案都可以实现仅激活状态变化的两个列表项重渲染,其余项完全不更新的效果。
内容的提问来源于stack exchange,提问作者Luc
相关产品推荐
相关产品推荐

