Mobx 仅在计算值更改时重新渲染项目
Posted
技术标签:
【中文标题】Mobx 仅在计算值更改时重新渲染项目【英文标题】:Mobx only re-render item when computed value changes 【发布时间】:2021-12-19 08:55:30 【问题描述】:我有一个媒体列表,我的目标是能够显示当前正在播放的媒体。
为此,我将播放媒体 ID 与列表中的 ID 进行比较,以应用正确的样式。 我的问题是,当单击另一个项目时,所有项目都会重新渲染,因为它们依赖于可观察的播放媒体。
class AppStore
...
get playingVideo()
if (!this.player.videoId || this.player.isStopped)
return null;
return this.videos[this.player.videoId];
const DraggableMediaItem = observer(( video, index ) =>
const store = useAppStore();
const isMediaActive = computed(
() => store.playingVideo && video.id === store.playingVideo.id
).get();
console.log("RENDER", video.id);
const onMediaClicked = (media) =>
if (!isMediaActive)
playerAPI.playMedia(media.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>
(provided, snapshot) => (
<ListItem
ref=provided.innerRef
...provided.draggableProps
...provided.dragHandleProps
style=getItemStyle(
snapshot.isDragging,
provided.draggableProps.style
)
button
disableRipple
...activeMediaProps
onClick=() => onMediaClicked(video)
>
<Stack direction="column" spacing=1 sx= width: "100%" >
<Stack direction="row" alignItems="center">
<ListItemAvatar>
<MediaAvatar video=video />
</ListItemAvatar>
<ListItemText primary=video.title />
<ListItemText
primary=durationToHMS(video.duration)
sx=
textAlign: "right",
minWidth: "max-content",
marginLeft: "8px",
/>
</Stack>
</Stack>
</ListItem>
)
</Draggable>
);
);
我认为将 isMediaActive
设为计算值可以防止这种情况发生,但由于计算值基于更改,因此会触发更新。
是否可以仅在计算值更改时重新渲染?
[编辑]
根据@danila 的评论,我清理了我的代码并注入了isActive
参数。但是,我一定还是遗漏了一些东西,因为当播放器的视频发生变化时,List
不会重新渲染。
那将是当前的伪代码:
const MediaItem = observer(( isActive ) =>
let activeMediaProps = ;
if (isActive)
activeMediaProps =
sx: backgroundColor: "rgba(246,250,254,1)" ,
;
return <ListItem ...activeMediaProps> ... </ListItem>;
);
const Playlist = observer(() =>
const store = useAppStore();
const items = store.playlist;
return (
<List>
items.map((item) => (
<MediaItem isActive=item.id === store.player.videoId />
))
</List>
);
);
[编辑 2]
带有工作示例的代码沙箱链接:
https://codesandbox.io/s/silent-lake-2lvdc?file=/src/App.js
提前感谢您的帮助和时间。
【问题讨论】:
【参考方案1】:首先你不能像那样使用computed
。在大多数情况下,computed
应该像商店中的财产一样使用。类似于observable
。
至于问题,如果您不希望项目重新呈现,您可以通过道具提供此标志,类似于伪代码中的内容
const List = observer(() =>
return (
<div>
items.map(item => (
<Item isMediaActive=store.playingVideo && item.id === store.playingVideo.id />
))
</div>
)
)
最好将该列表作为“独立”组件,不要只在整个视图中呈现项目。更多信息在这里https://mobx.js.org/react-optimizations.html#render-lists-in-dedicated-components
编辑:
还有另一种方式,实际上是“更多 MobX”的做事方式,就是在 item 对象本身中有 isPlaying
标志。但这可能需要您更改处理数据的方式,因此如果您已经设置了其他所有内容,第一个示例可能会更容易。
有了物品上的标志,您甚至不需要做任何其他事情,您只需检查它是否处于活动状态,MobX 会做其他所有事情。当您更改标志时,只有 2 个项目会重新呈现。您商店中的操作可能如下所示:
playItem(itemToPlay)
this.items.find(item => item.isPlaying)?.isPlaying = false
itemToPlay.isPlaying = true
【讨论】:
感谢您的帮助,我试过了,但传递给MediaItem
的道具仅在 List
重新渲染时更新。根据我发布的第一张图片,我想要实现的是,当点击最后一项时,只更新第二项和最后一项。
但这正是我的示例中会发生的情况,只会更新 2 个项目。是的,List
显然也会重新渲染。您不能同时拥有两者,您要么重新呈现列表,要么重新呈现所有项目。重新渲染列表当然更便宜。在我的示例中,只有 3 个组件将重新渲染(列表和 2 个项目),在您的示例中,所有项目都将重新渲染,例如,如果您有 100 个,那么所有 100 个将重新渲染,这远远超过 3
再次感谢您的帮助,我不想将属性添加到视频模型中,但是您的建议使我能够删除一些错误的逻辑,并且我的代码现在更干净了。我是这个和前端的新手,所以有些事情对我来说并不明显,对此感到抱歉。我已经用我所做的伪代码更新了我的帖子,你能检查一下并告诉我是否有任何问题吗?目前,更新store.player.videoId
后不会重新渲染(它是一个可观察的并且用于更新的操作)
好吧,现在很难说。您的代码看起来不错,如果其他一切都正确,它应该可以工作。如果您也可以发布其代码,那么您的商店可能有问题。如果您可以在codesandbox.io 上复制它,那就太理想了以上是关于Mobx 仅在计算值更改时重新渲染项目的主要内容,如果未能解决你的问题,请参考以下文章