渲染组件时,我一直收到唯一的关键子错误。我输入的数据有一个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}
/>
);
})}
答案 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.PureComponent
或React.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