【问题标题】:React: Map rendering component causing unnecessary re-renders, child key props not working?React:地图渲染组件导致不必要的重新渲染,子键道具不起作用?
【发布时间】:2018-11-27 06:23:19
【问题描述】:

在渲染组件时,我不断收到唯一键子错误。我传入的数据有一个 UUID,我将其用作每个孩子的关键道具。对于我拥有的每个孩子,react 都会重新渲染很多次。是什么导致重新渲染?

// 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()方法中的渲染方法,负责渲染&lt;Contact /&gt;

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

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

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

    更重要的是,我认为您误解了 key 属性的作用(或者更确切地说不作用)。特别是,key 属性在正常情况下不会阻止或减少组件的渲染次数。在您的情况下,每次您的顶级组件重新渲染时,chats 列表中的每个项目也将重新渲染。

    那么,如果它总是要重新渲染,key 的意义何在?嗯,理解这一点的关键是理解 react 所谓的 render 和它所谓的 reconciliation 的区别。 渲染 是在你的代码中执行渲染函数并生成虚拟 dom 的过程。虚拟 dom 只是普通的 javascript 对象,因此虽然该函数被称为“渲染”,但它实际上并没有将任何内容渲染到屏幕上。

    真正的魔法发生在所谓的和解中。这意味着反应引擎必须获取“渲染”的虚拟 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。第一种是遍历每个&lt;li&gt;,并更改文本内容以匹配新文本。在这个简单的示例中,这可能不是非常低效,但是如果每个列表项都是一大块复杂的 html 怎么办? Dom 操作很昂贵,如果只改变了项目的顺序,那么只需移动现有的 &lt;li&gt; 元素而不需要触摸内部内容可能会容易得多。

    这就是key 属性发挥作用的地方。 key 属性可帮助协调员了解更改的性质。现在每个&lt;li&gt; 都绑定到一个特定的键。如果键移动了虚拟 dom 中的位置,协调器现在可以简单地创建一组操作,将其移动到真实 &lt;li&gt; 中的相同位置。当列表中间的元素被删除时,这会更有帮助:

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

    使用key 属性,协调器现在可以找到被删除的确切&lt;li&gt;,然后将其从dom 中删除。如果没有 key 属性,协调器会查看虚拟 dom 中的第二个数组元素,然后说……嗯……第二个元素曾经是“John Doe”,但现在是“Bob Smith” ,我将不得不将第二个&lt;li&gt; 更新为现在阅读“Bob Smith”。然后它会看第三个元素说......嗯......第三个元素曾经是“Bob Smith”,现在是“Jane Doe”并编写dom操作再次更改文本。想象一下,如果你有 100 个名字,然后你删除了第二个。它现在必须进行 99 次 dom 更新。如果你有一个key 属性,它只会对第二个&lt;li&gt; 进行一次删除。

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

    【讨论】:

      【解决方案2】:

      您需要为使用 map 而不是在其中呈现的组件提供密钥。

      // 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 将被触发多次。

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

      【讨论】:

        【解决方案3】:

        你的密钥应该在你的父组件上

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

        对于您的重新渲染问题,请查看shouldComponentUpdate

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-12-02
          • 1970-01-01
          • 1970-01-01
          • 2019-03-13
          • 1970-01-01
          • 2021-09-01
          • 2018-08-12
          相关资源
          最近更新 更多