React - 使用 Memo 不阻止组件的重新渲染

React - Use Memo not preventing re-render of component

提问人:Coding Ninja123211 提问时间:11/3/2023 最后编辑:Coding Ninja123211 更新时间:11/3/2023 访问量:66

问:

在我的应用程序中,我有一个存储在 state 中的对象,它存储了一堆消息对象。我使用钩子来检索组件中的数据。我映射数据,并按如下方式显示:Chat

 {Object.keys(storedUsers).length !== 0  && chatId && Object.keys(chatMessages).length !== 0  && Object.values(chatMessages).map((messageObj, index) => {
               return <ChatMessage key={index} messageObj={messageObj} />
     })} 

ChatMessage 中,我有一个消息对象,我显示其数据。这是 ChatMessageComponent

const ChatMessage = ({messageObj}) => { 
        // MEMOIZED VALUE
        const newMessageObj = useMemo(() => {return messageObj})

        if (messageObj.admin === true) return 

   
        const sentById = newMessageObj.sentBy
        let sentByUser
        let isOwn

        if (sentById === userData.userId) {
         sentByUser = userData
         isOwn = true
        }
        else {
         sentByUser = storedUsers &&  storedUsers[sentById] ||  newChatData  && newChatData.otherUsersData
        }



        return ( 
            <div className='d-flex flex-row' style={{marginTop: 50}}>

                // VIDEO COMPONENT
                <Video messageObject={newMessageObj} />

             </div>
)

我在这个组件中有一个组件,我只想在装载时或消息 URL 更改时呈现它。<Video />

视频组件:

import React, { memo, useEffect } from 'react'


const Video = memo(({messageObject}) => {

    useEffect(() => {
        console.log('RE-RENDERED');
    }, [])

  return (
    <video>
        <source src={messageObject.url} />
    </video>
  )
},)

export default Video

            
             

但是,当组件中的不相关状态发生更改时,将重新呈现子项。我正在记住作为道具传递到组件中的消息对象。如何确保除非必要,否则 Video 组件不会重新渲染?是否缺少任何导致组件重新渲染的内容?谢谢。ChatMessagesVideo componentVideo

更新:

用于检索 ChatMessages 的选择器:

const chatMessages = useSelector((state) => state.messages.messagesData[chatId]) || {}
JavaScript 反应JS

评论

0赞 terpinmd 11/3/2023
sup' 用带有随机数的键?
0赞 Coding Ninja123211 11/3/2023
@terpinmd 请检查我更新的代码。我已将键替换为随机值,但是 Video 组件的重新渲染仍在发生
0赞 Coding Ninja123211 11/3/2023
@terpinmd 问题可能是我没有正确记忆,这导致了重新渲染?newMessageObj

答:

0赞 Brian Thompson 11/3/2023 #1

React 组件需要稳定。用作密钥是一种常见的反模式,但它实际上是创建密钥的几个选项中最糟糕的。这可能是记忆问题的根本原因。keyMath.random

提示是您的控制台日志:

useEffect(() => {
  console.log('RE-RENDERED');
}, [])

此控制台日志不会像消息所暗示的那样在重新渲染时触发,而只会在首次挂载组件时触发。由于您在“渲染”上看到这种触发,因此它实际上意味着每次父渲染时都会卸载并重新装载组件。

这正是组件更改时将发生的情况。告诉 react 你指的是组件的哪个实例。如果它看到相同的键,它就知道它是同一个实例 - 所以重新渲染它。如果它看到一个不同的键,它就知道这是一个组件 - 所以它会销毁旧组件并创建一个新实例。keykey

要解决您的问题,请找到一个合适的唯一稳定值。这可能是数组项中的内容,或者在最坏的情况下是数组项的内容。messageObjindex

.map((messageObj, index) => {
  // Ideally use something from the messageObj, but the index works in a pinch
  return <ChatMessage key={index} messageObj={messageObj} />
})} 

这里最大的收获是永远不要用作 .我经常在这里和其他地方的代码中看到它,因为它很快就摆脱了控制台中的警告,但它比仅仅使用数组索引要糟糕得多。Math.randomkey

评论

0赞 Nikos Issaris 11/3/2023
索引也不是一个好的键(也许是多个地图)。最好的方法是为每条消息设置一个唯一的 ID。然后通过key={message.id}
1赞 Brian Thompson 11/3/2023
我的回答解决了这个问题两次。我不知道消息对象里面有什么,也不知道数组是否真的有可能随时间而改变(使用数组索引可能有害的原因)。答案解决了 OP 面临的根本问题,并提供了以适当方式纠正问题的指导
0赞 Coding Ninja123211 11/3/2023
@BrianThompson 您好,我已经相应地更新了我的代码,但是该组件仍在继续重新渲染。我知道这是由于视频组件中的控制台.log
0赞 Brian Thompson 11/3/2023
console.log 不会导致重新渲染,并且 useEffect 不会在重新渲染时运行。它在组件挂载上运行。如果您仍然更频繁地看到该日志,那么您应该,您仍然有一个错误。你在更高的层次上做同样的事情吗?key
0赞 Coding Ninja123211 11/3/2023
@BrianThompson 是的,由于重新渲染,控制台日志正在运行。但是,我已经相应地更改了密钥,并且没有更多相关的密钥。问题可能出在我调用从现有对象创建新对象的行上吗?useMemo