Mobx 仅在计算值更改时重新渲染项目
Mobx only re-render item when computed value changes
我有一个媒体列表,我的目标是能够显示当前正在播放的媒体。
为此,我将播放媒体 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]
代码沙箱 link 有一个工作示例:
https://codesandbox.io/s/silent-lake-2lvdc?file=/src/App.js
提前感谢您的帮助和时间。
首先你不能那样使用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”的做事方式,就是在项目对象本身中有 isPlaying
标志。但这可能需要您更改处理数据的方式,因此如果您已经设置了其他所有内容,第一个示例可能会更容易。
有了项目上的标志,您甚至不需要做任何其他事情,您只需检查它是否处于活动状态,MobX 将完成其他所有事情。当您更改标志时,只有 2 个项目会重新呈现。您商店中的操作可能如下所示:
playItem(itemToPlay) {
this.items.find(item => item.isPlaying)?.isPlaying = false
itemToPlay.isPlaying = true
}
我有一个媒体列表,我的目标是能够显示当前正在播放的媒体。
为此,我将播放媒体 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]
代码沙箱 link 有一个工作示例:
https://codesandbox.io/s/silent-lake-2lvdc?file=/src/App.js
提前感谢您的帮助和时间。
首先你不能那样使用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”的做事方式,就是在项目对象本身中有 isPlaying
标志。但这可能需要您更改处理数据的方式,因此如果您已经设置了其他所有内容,第一个示例可能会更容易。
有了项目上的标志,您甚至不需要做任何其他事情,您只需检查它是否处于活动状态,MobX 将完成其他所有事情。当您更改标志时,只有 2 个项目会重新呈现。您商店中的操作可能如下所示:
playItem(itemToPlay) {
this.items.find(item => item.isPlaying)?.isPlaying = false
itemToPlay.isPlaying = true
}