【问题标题】:React: Weird reconciliation result on list items whose content is served by array with same length as the listReact:列表项的奇怪协调结果,其内容由与列表长度相同的数组提供
【发布时间】:2020-12-14 18:30:18
【问题描述】:

更新: 问题没有解决,但结果与我第一次发布时的样子不同。我修改了标题以更好地描述它的真正含义。更新了代码中的一些 cmets 以节省一些读取时间。 React 会在应该重用元素的时候重用元素,当两个数组长度相同时,结果会很奇怪。

Codesandbox 快速查看。

我遇到了这个令人沮丧但有趣的问题,并且已经为此苦苦挣扎了很长一段时间,但没有运气。不确定它是否真的是 React 的错误。

根据官方文档here,如果我理解这一点,通过为元素提供正确的键(在同级中唯一),React 应该尝试更新和重用它,而不是在重新渲染。

查看codepen 上的此示例,我已将其简化为足以显示问题。原始代码也包含在下面以供快速参考。

如果应用程序像现在这样简单,它就可以正常工作,因此增加/减少状态cur(通过单击两个按钮)来回滚动数字。

如果您在浏览器开发工具中查看它,以查看每次单击按钮时会破坏和重新创建哪些元素,很明显每次单击 + 都会破坏第一个项目并创建最后一个项目,每次单击 @987654327 @ 销毁最后一项并创建第一项,中间的其他 <li>s 只是更新和重用。这是正确的,也是意料之中的。

问题:

如果 numloop 恰好长度相同,例如将num 更改为[3, 4, 5, 6](删除任何两个项目都可以)所以它有4 个项目与loop 相同,我希望看到所有项目在按钮单击时被重用,并且不应该销毁和重新创建任何项目,因为所有项目都在同一个 DOM 树中,并且设置了易于识别的键。

但是,如果您再次检查浏览器开发工具,它的工作方式确实完全不同,尽管渲染结果似乎没有任何问题。单击+ 会破坏第一个项目并将其重新创建为最后一个项目,同时重用所有其他项目。点击- 会重用第一个项目(上次渲染的最后一个项目)并销毁/重新创建所有其他项目。

虽然应用程序仍然可以正常运行(因为它很简单,只显示一些数字),但是所有类驱动的转换都会搞砸,<li> 下的所有子元素都会重新初始化,例如<img> 重新加载源图片。

问题

  1. 在这种情况下,当两个数组长度相同时,有什么特别之处?这里到底发生了什么?

  2. 可以或应该做些什么来解决它?意思是尝试尽可能多地重用现有项目。我能想到的一种快速肮脏的方法是将一些虚拟项目插入num 并在 map() 迭代中添加一些逻辑以跳过虚拟项目。这行得通,但我不认为这是一种“正确”的方式。

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

const num = [1, 2, 3, 4, 5, 6]; // array to serve content to list items
const loop = [1, 2, 3, 4]; // array to be mapped to generate the list

const RollingNums = () => {
  const [cur, setCur] = React.useState(0); // control number rolling

  return (
    <div className="problem">
      <ul>
        {loop.map((value, index) => { // list items count is fixed
          // simple CS to get continued content from array num
          let idx = index + cur;
          idx =
            idx >= 0
              ? idx % num.length
              : ((idx % num.length) + num.length) % num.length;
          const target = num[idx].toString(); // content from array num for current element
          return (
            <li key={target} className={target}>
              {target}
            </li>
          );
        })}
      </ul>
      <div className="control">
        <button onClick={() => setCur((prev) => prev - 1)}>-</button>
        <span>{cur}</span>
        <button onClick={() => setCur((prev) => prev + 1)}>+</button>
      </div>
    </div>
  );
};

ReactDOM.render(<RollingNums />, document.querySelector('#root'));

【问题讨论】:

  • 您的密钥在每次重新渲染时都会发生变化。你希望 React 如何使用相同的最后一个 React 元素?尽管如此,请check this out
  • @PrãtéékThápá 感谢您的提示。在我开始阅读您建议的文章之前,请允许我稍微评论一下:是的,我在每次渲染时都分配了键,但不是新键,而是选择并分配了现有的键。事实上,只要 num 比 loop 有更多的项目,一切都会按预期工作。 React 确实重用/更新了上次渲染中的相同元素,我已经尽可能清楚地描述了这一点。仅当 num 和 loop 长度相同时才会出现此问题。
  • 据我所知,您正在分配新键。例如,第一个 li 有一个不同的键,并且会与其他 li 重新渲染。不确定,不过我明白你的意思。
  • 我相信 React 是通过键而不是命令来工作的。在每个渲染上分配键是指导 React 在某些子元素上做什么的自然方式。观察 num 的长度大于 loop 的情况完全反映了这一点,只是当两个数组长度相同时,事情开始出乎意料...

标签: javascript reactjs list key reusability


【解决方案1】:

似乎是 React 的一些问题,它确实以某种方式删除然后重新创建这些项目。检查this,尤其是Kunuk Nykjær 提供的codesandbox

在我的工作中,我只是通过扩展第一个数组 (num) 来确保它比第二个数组 (loop) 长,在这种情况下 React 协调确实按预期工作。在我的情况下,这需要很多额外的检查,但我现在只能做这些。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多