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 仅在计算值更改时重新渲染项目的主要内容,如果未能解决你的问题,请参考以下文章

计算值更改时组件不重新渲染

项目更改时反应本机部分列表不重新渲染

React+TypeScript+MobX 遇到的一些问题

仅在重新渲染组件时才反应状态更新

WPF MVVM Command CanExecute,仅在焦点更改时重新评估

仅在上下文值更新时有条件地重新渲染