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
}