首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Preact呈现的错误组件

Preact呈现的错误组件
EN

Stack Overflow用户
提问于 2017-03-14 05:39:13
回答 1查看 8.1K关注 0票数 45

我使用Preact (实际上是React)来呈现一个保存在状态数组中的项目列表。每一项旁边都有一个删除按钮。我的问题是:当单击按钮时,正确的项被删除(我多次验证过这一点),但项被重新呈现时,最后一个项丢失了,而被删除的项仍然在那里。我的代码(简化):

import { h, Component } from 'preact';
import Package from './package';

export default class Packages extends Component {
  constructor(props) {
    super(props);
    let packages = [
      'a',
      'b',
      'c',
      'd',
      'e'
    ];
    this.setState({packages: packages});
  }

  render () {
    let packages = this.state.packages.map((tracking, i) => {
      return (
        <div className="package" key={i}>
          <button onClick={this.removePackage.bind(this, tracking)}>X</button>
          <Package tracking={tracking} />
        </div>
      );
    });
    return(
      <div>
        <div className="title">Packages</div>
        <div className="packages">{packages}</div>
      </div>
    );
  }

  removePackage(tracking) {
    this.setState({packages: this.state.packages.filter(e => e !== tracking)});
  }
}

我做错了什么?我需要以某种方式主动重新渲染吗?这是一个n+1案例吗?

Clarification:我的问题不是状态的同步性。在上面的列表中,如果我选择删除'c',状态将正确更新为['a','b','d','e'],但呈现的组件是['a','b','c','d']。每次调用removePackage时,都会从数组中删除正确的一个,显示正确的状态,但会呈现一个错误的列表。(我删除了console.log语句,所以看起来它们不是我的问题)。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-03-14 08:08:22

这是一个典型的问题,Preact的文档完全没有提供足够的服务,所以我想亲自为此道歉!如果有人感兴趣,我们一直在寻求帮助编写更好的文档。

这里发生的情况是,您正在使用数组的索引作为键(在渲染内的贴图中)。这实际上只是模拟了VDOM diff在默认情况下的工作方式-键始终是0-n,其中n是数组长度,因此删除任何项只会将最后一个键从列表中删除。

说明:关键点超越渲染

在您的示例中,想象一下(虚拟) DOM在初始呈现时的样子,然后在删除项"b“(索引3)之后。下面,让我们假设你的列表只有3个项目(['a', 'b', 'c']):

以下是初始渲染产生的结果:

<div>
  <div className="title">Packages</div>
  <div className="packages">
    <div className="package" key={0}>
      <button>X</button>
      <Package tracking="a" />
    </div>
    <div className="package" key={1}>
      <button>X</button>
      <Package tracking="b" />
    </div>
    <div className="package" key={2}>
      <button>X</button>
      <Package tracking="c" />
    </div>
  </div>
</div>

现在,当我们在列表中的第二项上单击"X“时,"b”被传递给removePackage(),它将state.packages设置为['a', 'c']。这会触发我们的渲染,生成以下(虚拟) DOM:

<div>
  <div className="title">Packages</div>
  <div className="packages">
    <div className="package" key={0}>
      <button>X</button>
      <Package tracking="a" />
    </div>
    <div className="package" key={1}>
      <button>X</button>
      <Package tracking="c" />
    </div>
  </div>
</div>

由于VDOM库只知道您在每次渲染时给它的新结构(不知道如何从旧结构更改为新结构),键所做的基本上是告诉它项01保持不变-我们知道这是不正确的,因为我们希望删除索引1处的项。

请记住:key优先于默认子diff重新排序语义。在本例中,因为key总是从0开始的数组索引,所以最后一项(key=2)会被删除,因为它是后续呈现中缺少的一项。

解决之道

因此,为了修正你的例子--你应该使用一些标识项目的东西,而不是它的偏移量作为你的键。这可以是项本身(可以接受任何值作为键),也可以是.id属性(首选,因为它避免了分散对象引用,这会阻止GC):

let packages = this.state.packages.map((tracking, i) => {
  return (
                                  // ↙️ a better key fixes it :)
    <div className="package" key={tracking}>
      <button onClick={this.removePackage.bind(this, tracking)}>X</button>
      <Package tracking={tracking} />
    </div>
  );
});

呼,这比我预期的要冗长得多。

TL,DR:从不使用数组索引(迭代索引)作为key。在最好的情况下,它是在模仿默认行为(自上而下子对象重新排序),但更常见的情况是,它只是将所有差异都推到最后一个子对象上。

编辑: @tommy recommended 优秀的link to the eslint-plugin-react docs,它比我上面做的更好地解释它。

票数 102
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42773892

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档