簡體   English   中英

Preact渲染的組件錯誤

[英]Wrong components rendered by Preact

我正在使用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案件嗎?

澄清 :我的問題不在於國家的同步性。 在上面的列表中,如果我選擇刪除'c',狀態會正確更新為['a','b','d','e'] ,但呈現的組件是['a','b','c','d'] 每次調用removePackage ,都會從數組中刪除正確的一個,顯示正確的狀態,但會呈現錯誤的列表。 (我刪除了console.log語句,所以看起來它們不是我的問題)。

這是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優先於默認的子差異重新排序語義。 在這個例子中,因為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推薦這個eslint-plugin-react文檔的優秀鏈接,它比我上面做的更好地解釋它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM