Ответ 1
Это классическая проблема, которая полностью недоказана документацией Preact, поэтому я хотел бы лично извиниться за это! Мы всегда ищем помощь при написании лучшей документации, если кто-то заинтересован.
Что здесь произошло, так это то, что вы используете индекс своего массива в качестве ключа (на вашей карте в рендере). Это фактически просто эмулирует, как работает VDOM diff по умолчанию - ключи всегда 0-n
, где n
- длина массива, поэтому удаление любого элемента просто удаляет последний ключ из списка.
Объяснение: Ключи transcend render
В вашем примере представьте, как (виртуальная) 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 знает только о новой структуре, которую вы даете ей для каждого рендеринга (а не как перейти от старой структуры к новой), то, что сделали ключи, в основном говорит, что элементы 0
и 1
остался на месте - мы знаем, что это неверно, потому что мы хотели удалить элемент с индексом 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, которые лучше объясняют это, чем я сделал выше.