【问题标题】:Mobx only re-render item when computed value changesMobx 仅在计算值更改时重新渲染项目
【发布时间】: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

提前感谢您的帮助和时间。

【问题讨论】:

    标签: reactjs mobx react-functional-component mobx-react computed-observable


    【解决方案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 上复制它,那就太理想了
    猜你喜欢
    • 2019-09-15
    • 1970-01-01
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 2016-11-04
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    相关资源
    最近更新 更多