Разделите ключ и позицию
Флажок в useState принадлежит смонтированной строке. После разворота коллекции отметка должна остаться у своего элемента, а не у первой позиции на экране. Передайте в list устойчивый getKey, а номер для отображения вычисляйте отдельно.
import {createElement as h} from "@fiberfx/hyperscript";
import {useState} from "@fiberfx/hyperscript/hooks";
import {event, store} from "@fiberfx/hypergraph";
import {Checkbox} from "@fiberfx/ui";
import {list} from "@fiberfx/reflect";
type Item = {id: string; title: string};
const removed = event<string>();
const reversed = event<void>();
const $items = store<Item[]>([
{id: "a", title: "Alpha"}, {id: "b", title: "Beta"},
]).on(removed, (items, id) => items.filter(item => item.id !== id))
.on(reversed, items => [...items].reverse());
const Row = (p: {item: Item; position: number; remove: () => void}) => {
const [checked, setChecked] = useState(false);
return h("li", null,
h(Checkbox, {label: `${p.position}. ${p.item.title}`, checked, onChange: setChecked}),
h("button", {onClick: p.remove}, "Remove"));
};
export const Rows = list({
source: $items, view: Row, getKey: item => item.id,
mapItem: {
item: item => item,
position: (_, index) => index + 1,
remove: item => () => removed(item.id),
},
});
Выведите h("ul", null, h(Rows, {})) внутри Provider приложения. Функция remove, полученная из отображения элемента, привязана к области, поэтому событие меняет нужную коллекцию. Флажок в этом примере хранится в представлении; выбор, который должен сохраняться как данные приложения, обычно относится к модели.
mapItem заменяет свойства элемента. Он не добавляет {id, title} к результату отображения выше. Без mapItem объекты коллекции передаются как свойства строк. Общий bind задаёт значения по умолчанию; свойства строки имеют над ними приоритет.
Переместите отмеченную строку
Откройте эксперимент со списком. Отметьте Alpha и нажмите Развернуть порядок. Верните порядок, удалите Beta и после каждого действия проверьте отметку и номер Alpha.
Нажмите Попробовать одинаковый ключ. В этой сцене приложение проверяет кандидата до передачи в отображаемый список и сохраняет последнюю корректную коллекцию. Сообщение показывает работу проверки приложения, а не перехваченное исключение рендера.
Проследите сохранение строки
Флажок Alpha должен остаться отмеченным после перестановки, а номер позиции — измениться. Удаление Beta должно затронуть только Beta. Удаление Alpha завершит время жизни её строки; последующее восстановление создаст строку с новым локальным состоянием.
В регрессионном тесте сохраните DOM-узел Alpha до перестановки и сравните его после неё, отдельно от проверки состояния. Правильный текст не обнаружит лишнее размонтирование. Новый объект элемента с тем же ключом может обновить свойства без замены строки.
Отклоните неверную коллекцию
Сам пакет отклоняет повторяющиеся нормализованные ключи, числовые ключи NaN и Infinity, источник не-хранилище и состояние не-массив. 1 и "1" дают одинаковый ключ; 1 и "01" — разные. Эти отказы проверяйте отдельно от защитной проверки приложения в эксперименте.
Без mapItem нельзя передавать примитивы, null и массивы как элементы. Отображение должно покрыть обязательные свойства, которые не покрыты bind или mapProps; пересекаться по ключам с mapProps оно не может. Для представления с дискриминированным объединением передавайте готовые объекты либо обёртку item: независимые отображения полей не доказывают их согласованность.
Осознанно восстановите список
Нажмите Начать упражнение заново, чтобы вернуть исходные строки и очистить их локальные флажки. Сброс только модели с сохранением смонтированных ключей не обязан очищать состояние представлений. Выберите нужный смысл сброса для своего продукта.
Повторите опыт с отмеченной Gamma вместо Alpha. Затем удалите отмеченную строку и убедитесь, что её отметка не перешла к другому элементу.
Соберите редактируемую очередь
Дайте каждой записи очереди устойчивый предметный идентификатор. Видимый номер вычисляйте через mapItem, но не используйте его как идентичность, если возможны вставки и перестановки. Без getKey список намеренно использует позиции; это подходит только действительно позиционным коллекциям.
Добавьте пустой экран через variant, а состояния загрузки и ошибки оставьте вне отображения строки. Следующая глава задаёт эти альтернативные экраны явно.