【问题标题】:Continuous Rendering even when value does not change即使值不变,也能连续渲染
【发布时间】:2021-04-05 20:28:08
【问题描述】:

问题

如果传递给 setCurrentlyPlaying() 的状态不同,我的组件每秒渲染一次,而不是仅渲染一次。如何解决此问题?

背景

我想每 1 秒检查一次 API。除了第一次渲染,我只希望我的组件在incomingSong 与currentPlayingSong 不同时重新渲染。如果传入的状态与基于 Object.is 比较算法的先前状态不同,我依靠 State 钩子仅触发重新渲染的能力。即使我只返回(<div></div>);在我的组件中,它继续每 1 秒渲染一次,因此问题似乎源于此逻辑。我通过在我的 Spotify 上播放 NOTHING 来测试它,所以它总是进入第一个 if 语句并且它仍然每秒重新渲染(打印 console.log("RENDERED") 语句)。

    const [currentlyPlayingSong, setCurrentlyPlayingSong] = useState({ type: 'Now Playing', name: 'No Song Playing', artists: [], imageUrl: ''});

    console.log("RENDERED")

    useEffect(() => {
        console.log("initial effect including updateSongs");

        const updateSongs = async () => {
            const result = await getCurrentlyPlaying();

            var incomingSong = {};

            // no song is playing
            if(result.data === "") {
                incomingSong = { type: 'Now Playing', name: 'No Song Playing', artists: [], imageUrl: ''};
            }
            // some song is playing
            else {
                var songJSONPath = result.data.item;
                incomingSong = {
                    type: 'Now Playing', 
                    name: songJSONPath.name,
                    id: songJSONPath.id,
                    artists: songJSONPath.artists.map(artist => artist.name + " "),
                    imageUrl: songJSONPath.album.images[songJSONPath.album.images.length - 2].url,
                };
            }

            // console.log("currentlyPlayingSong: ", currentlyPlayingSong)
            // console.log("incomingSong: ", incomingSong);

            // this line!!!!
            setCurrentlyPlayingSong(incomingSong);
        }

        updateSongs();

        const timer = setInterval(updateSongs, 1000);

        return () => clearInterval(timer);
    },[]);

【问题讨论】:

  • 功能组件主体中的控制台日志实际上并不是组件渲染频率的准确度量对于 DOM,您应该在 useEffect 挂钩中执行此操作。此外,updateSongs 每次调用时都会创建一个新的 incomingSong 对象引用,并且由于 react 使用浅引用相等,因此每次 setCurrentlyPlayingSong(incomingSong); 时都会触发重新渲染。您应该在 使用新值更新状态之前手动执行此条件检查。如何确定对象之间的相等性取决于您。 id 似乎是个不错的选择。
  • @sj123 但是你总是将一个新对象传递给setCurrentlyPlayingSong 所以,React 无法理解它得到了相同的歌曲。

标签: javascript reactjs react-hooks


【解决方案1】:

功能组件主体中的控制台日志记录实际上并不是组件被渲染到 DOM 的频率的准确度量 strong>,您应该在 useEffect 挂钩中执行此操作。

另外,updateSongs 每次调用时都会创建一个新的 incomingSong 对象引用,并且由于 react 使用浅引用相等,因此每次 setCurrentlyPlayingSong(incomingSong); 时都会触发重新渲染。您应该在 使用新值更新状态之前手动执行此条件检查。如何确定对象之间的相等性取决于您。 id 似乎是个不错的选择。

检查id 的歌曲是否不同,只有当它实际上是不同的歌曲时才更新状态。

if (currentlyPlayingSong.id !== incomingSong.id) {
  setCurrentlyPlayingSong(incomingSong);
}

代码

const [currentlyPlayingSong, setCurrentlyPlayingSong] = useState({ 
  type: 'Now Playing',
  name: 'No Song Playing',
  artists: [],
  imageUrl: '',
});

useEffect(() => {
  console.log("RENDERED"); // <-- logs when component is rendered to DOM
});

useEffect(() => {
  console.log("initial effect including updateSongs");

  const updateSongs = async () => {
    const result = await getCurrentlyPlaying();

    let incomingSong = {};

    // no song is playing
    if (result.data === "") {
      ...
    }
    // some song is playing
    else {
      ...
    }

    // check if song id is different and only update if true
    if (currentlyPlayingSong.id !== incomingSong.id) {
      setCurrentlyPlayingSong(incomingSong);
    }
  }

  updateSongs();

  const timer = setInterval(updateSongs, 1000);

  return () => clearInterval(timer);
},[]);

【讨论】:

  • 非常感谢!我没有注意到它每次都是一个新对象。它现在按我想要的方式工作。我最终使用了 lodash _.isEqual() 方法。另外,没有意识到 useEffect() 之外的 console.log() 是不准确的。
【解决方案2】:

假设一首歌曲的名称表示一首歌曲的身份。您可以在更新状态之前添加 if 语句。

if (currentlyPlayingSong.name !== incomingSong.name) {
 setCurrentlyPlayingSong(incomingSong);
}

这将确保仅在歌曲更改时更新状态。如果name 不是要检查的正确属性,则将其替换为正确的属性。

React 无法理解它可以阻止状态更新,因为您每次都在传递一个新对象。请参阅this 了解更多关于 react 比较不​​同值的信息。

【讨论】:

  • 感谢您的帮助。我没有正确理解相等性。