反应:地图渲染组件导致不必要的重新渲染,子项道具不起作用?

时间:2018-11-27 06:23:19

标签: javascript reactjs

渲染组件时,我一直收到唯一的关键子错误。我输入的数据有一个UUID,用作每个孩子的主要道具。对于我拥有的每个孩子,反应都要重新渲染很多次。是什么原因导致重新渲染?

// Contact.js

import React from 'react';

const Contact = ({ user, chat, activeChat, setActiveChat }) => {

    if (!chat.id) return null;

    const lastMessage = chat.messages[chat.messages.length - 1];
    const chatSideName =
        chat.users.find(name => name !== user.username) || 'General';
    const classNames = activeChat && activeChat.id === chat.id ? 'active' : '';

    return (
        <div
            key={chat.id}              // ebd4698f-c4b2-4dfe-92b5-2f6636d98db8
            className={`user ${classNames}`}
            onClick={() => {
                setActiveChat(chat);
            }}
        >
            <div className="user-photo">{chatSideName[0].toUpperCase()}</div>
            <div className="user-info">
                <div className="name">{chatSideName}</div>
                {lastMessage && (
                    <div className="last-message">{lastMessage.message}</div>
                )}
            </div>
        </div>
    );
};

export default Contact;

这是容器组件的render()方法中负责渲染<Contact />的渲染方法

// SideBar.js
{chats.map(chat => {
    return (
        <Contact
            chat={chat}                                            
            user={user}
            activeChat={activeChat}
            setActiveChat={setActiveChat}
        />
    );
})}

3 个答案:

答案 0 :(得分:2)

您需要为使用地图而不是在地图中渲染的组件提供关键。

// SideBar.js
{chats.map((chat, index) => {
    return (
        <Contact
            key = {chat.id || index}
            chat={chat}                                            
            user={user}
            activeChat={activeChat}
            setActiveChat={setActiveChat}
        />
    );
})}

此外,您的Contact组件将具有与chats数组中的元素数量一样多的实例,因此Contact中的呈现方法console.log将被触发多次。

要进一步优化您的应用,您可以将Contact组件使用React.PureComponentReact.memo

答案 1 :(得分:1)

我要指出的几件事。 Shubham和Fawzi指出的第一点是:key属性应该始终在您从map()函数返回的顶级组件上设置,而不是嵌套在其中。

但是,更重要的是,我认为您误解了key属性的作用(或更确切地说是不起作用)。特别是,key属性在正常情况下不会阻止或减少呈现组件的次数。对于您而言,每次重新渲染顶级组件时,chats列表中的每一项都会重新渲染。

因此,如果总是要重新渲染,key的意义是什么?好了,理解这一点的关键是要理解什么是React称为 render 和它所说的 reconciliation 渲染是仅在您的代码中执行渲染功能并生成虚拟dom的过程。虚拟dom只是普通的javascript对象,因此尽管该函数称为“渲染”,但实际上并没有在屏幕上渲染任何内容。

真正的魔力发生在所谓的和解中。这意味着React引擎必须采用“渲染的”虚拟dom,将其与浏览器中的真实dom进行比较,找出发生了什么变化,并尝试提出更改浏览器dom以使其匹配的指令。新的虚拟dom。

列表的问题在于,在不知道更改是由于插入,删除还是仅就地编辑的情况下优化这些更改可能会很棘手。例如,列出名称列表:

["John Doe", "Jane Doe", "Alice Smith", "Bob Smith"]

呈现为:

<ul>
     <li>John Doe</li>
     <li>Jane Doe</li>
     <li>Alice Smith</li>
     <li>Bob Smith</li>
 </ul>

然后更改顺序:

["Alice Smith", "John Doe", "Bob Smith", "Jane Doe"]

它现在应该看起来像:

<ul>
     <li>Alice Smith</li>
     <li>John Doe</li>
     <li>Bob Smith</li>
     <li>Jane Doe</li>
 </ul>

和解员可以采用多种方式来获取第一个dom,然后将其转换为第二个dom。第一种是只浏览每个<li>,然后更改文本内容以匹配新文本。在这个简单的示例中,这可能并非效率低下,但是如果每个列表项都是复杂的html大块怎么办? Dom操作非常昂贵,如果更改的只是项目的顺序,那么只需移动现有的<li>元素而无需触摸内部内容,可能会容易得多。

这是key属性的位置。 key属性可帮助协调人了解更改的性质。现在,每个<li>都绑定到一个特定的键。如果密钥在虚拟dom中移动了位置,协调器现在可以简单地创建一组动作,将其移动到真实<li>中的相同位置。当删除列表中间的元素时,这可以提供更多帮助:

["Alice Smith", "Bob Smith", "Jane Doe"]

有了key属性,对帐员现在可以找到已删除的确切<li>并将其从dom中删除。如果没有key属性,协调器将查看虚拟dom中的第二个数组元素,然后说……。。。第二个元素以前是“ John Doe”,但现在是“ Bob”。史密斯”,我将不得不更新第二个<li>来阅读“鲍勃·史密斯”。然后,它会看着第三个元素说……嗯……第三个元素以前是“ Bob Smith”,现在是“ Jane Doe”,并编写dom操作来再次更改文本。想象一下,如果您有100个名称,而您删除了第二个。现在它将必须执行99个dom更新。如果您具有key属性,则只需对第二个<li>进行一次删除。

总而言之,key属性不会阻止在您的代码中调用render函数。防止这种情况的唯一方法是使用纯组件或实现componentShouldUpdate()key属性的作用是使对帐(即dom操纵部分)的执行速度更快。

答案 2 :(得分:0)

您的密钥应在您的父组件上

{chats.map(chat => {
    return (
        <Contact
            key={chat.id}          
            chat={chat}                                            
            user={user}
            activeChat={activeChat}
            setActiveChat={setActiveChat}
        />
    );
})}

有关您的渲染问题,请查看shouldComponentUpdate