Различные способы добавить ключ к элементу JSX в цикле в React
Я работаю над реакцией уже больше года. Я в основном играл с итерацией массива, используя.map, .forEach, .filter или используя Object.keys и Object.values, если это объект.
Но каковы разные способы добавления уникального ключа в элемент jsx. Вот то, к чему я привык до сих пор
Использование уникального идентификатора из данных в качестве ключа к ключевому слову:
const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];
render(){
const items = data.map(item => {
return <span key={item.id}>{item.name}</span>;
}
return(
<div>
{items}
</div>
)
}
Использование индекса в качестве ключа для ключевого слова:
const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];
render(){
const items = data.map((item, i) => {
let keyValue = i+1;
return <span key={keyValue}>{item.name}</span>;
}
return(
<div>
{items}
</div>
)
}
Есть ли другие способы добавить уникальный ключ к элементу jsx помимо того, что я упомянул выше, и какой из них наиболее эффективен и рекомендован?
4 ответа
Прежде всего, избегайте использования случайных ключей.
Есть много способов написания ключей, и некоторые будут работать лучше, чем другие.
Чтобы понять, как выбранные нами ключи влияют на производительность, необходимо понять алгоритм согласования React.
https://reactjs.org/docs/reconciliation.html
tl; dr Вводит эвристику для сравнения деревьев Virtual DOM, чтобы сделать это сравнение O(n) с n узлами этого дерева VDOM. Эту эвристику можно разделить на следующие пункты:
- Компоненты другого типа создадут новое дерево: это означает, что при сравнении старого дерева с новым, если примиритель обнаружит, что узел действительно изменил свой тип (например,
<Button />
в<NotButton />
), приведет к тому, что наш Button будет размонтирован с дочерними элементами, а NotButton - с дочерними. - Мы можем подсказать React о том, как экземпляры сохраняются в VDOM, избегая их повторного создания. Эти подсказки предоставляются нами с ключами. После принятия решения о том, следует ли сохранить экземпляр в узле (поскольку его тип остается прежним), примиритель будет выполнять итерацию по дочерним узлам этого узла, чтобы сравнить их.
Предположим теперь, что у нас есть это:
<div>
<Button title="One" />
<Button title="Two" />
</div>
И мы хотели бы добавить кнопку в DOM на следующем рендере, скажем
<div>
<Button title="Zero" />
<Button title="One" />
<Button title="Two" />
</div>
Алгоритм будет выглядеть следующим образом:
- Сравнивает
<divs>
в обоих VDOM. Поскольку они имеют одинаковый тип, нам не нужно заново создавать новое дерево. Реквизиты те же, поэтому на данный момент нет изменений, которые можно применить к DOM. - кнопка
One
сравнивает сZero
, Reconciler обнаруживает, что здесь произошла смена реквизита, затем обновляет DOM с этим названием. - кнопка
Two
сравнивает сOne
, Reconcilier также обнаруживает изменение реквизита и использует DOM для записи этого изменения. - Обнаруживает, что новый
Button
добавляется как последний дочерний элемент, поэтому создает новыйButton
экземпляр в VDOM и записать это изменение в DOM.
Обратите внимание, что они выполняют много операций в DOM, поскольку сравнивают компоненты по их индексу.
Теперь мы можем исправить это поведение, сообщив нашему посреднику, что эти экземпляры следует использовать повторно. Теперь, давайте иметь это:
<div>
<Button title="One" key="One" />
<Button title="Two" key="Two" />
</div>
И мы хотели бы добавить кнопку в DOM на следующем рендере, скажем
<div>
<Button title="Zero" key="Zero" />
<Button title="One" key="One" />
<Button title="Two" key"Two" />
</div>
Алгоритм будет выглядеть следующим образом:
- Сравнивает
<divs>
в обоих VDOM. Поскольку они имеют одинаковый тип, нам не нужно заново создавать новое дерево. Реквизиты те же, поэтому на данный момент нет изменений, которые можно применить к DOM. - Берет первого ребенка из детей. 'Это
Button
', говорит примиритель. "И есть ключ" ("One"). Затем ищет детей с тем же ключом в новом списке детей. "О, я столкнулся с этим! но примиритель понимает, что его реквизит не изменился. Тогда никакие операции DOM не будут необходимы для этого. - Тот же сценарий происходит со вторым
Button
, это будет сравнивать поkeys
вместоindex
, Понимает, что это тот же экземпляр, и никакие реквизиты не были изменены, поэтому React решает не применять изменения в DOM. - Для
Button
с нулевым ключом, поскольку не существует дочернего элемента с таким же ключом, понимает, что экземпляр должен быть создан в VDOM, и это изменение должно быть записано в DOM.
Таким образом, использование ключей с предсказуемым содержимым помогает примирителю выполнять меньше операций с DOM. Здоровые ключи - это те, которые могут быть выведены из отображаемого объекта, например name
или id
или даже url
если мы преобразуем urls
в <imgs />
,
Как насчет key=index
? Не будет иметь никакого эффекта, так как по умолчанию, recciler сравнивает по позиции, то есть по индексу.
Эти ключи должны быть глобально уникальными? Не обязательно. Они должны быть уникальными среди братьев и сестер, чтобы их можно было различить при итерации по дочерним узлам.
Как насчет случайных ключей? Этого следует избегать любой ценой. Если ключ изменяется при каждом рендеринге, это будет приводить к тому, что React будет уничтожать и создавать экземпляры в VDOM (и, следовательно, делать дополнительные записи в DOM), поскольку компонент с ключом не найден среди новых дочерних элементов, но новый с тем же типом.
Если результат рендеринга
<div>
<Button key={randomGenerator()} />
</div>
Потом каждый раз render
выполняется (например, из-за изменения реквизита / состояния, или даже если его родитель перерисовывается и наш shouldComponentUpdate
возвращается true
), новый randomGenerator()
ключ будет сгенерирован. Это будет выглядеть так:
'Привет! Я нашел Button
с F67BMkd==
ключ, но ни один не был найден в следующем. Я удалю это. 'Ой! Я столкнулся с Button
с SHDSA++5
ключ! Давайте создадим новый ".
Всякий раз, когда посредник сообщает, что экземпляр должен быть удален и размонтирован, его внутреннее состояние будет потеряно; даже если мы смонтируем его снова. Экземпляр в VDOM не будет сохранен в этом случае.
Button
было то же самое, но посредник сделал беспорядок в DOM.
Надеюсь, поможет.
Лучший способ выбрать ключ - использовать строку, которая однозначно идентифицирует элемент списка среди его братьев и сестер. Чаще всего вы используете идентификаторы из ваших данных в качестве ключей:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
Если у вас нет стабильных идентификаторов для отображаемых элементов, вы можете использовать индекс элемента в качестве ключа в качестве крайней меры:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
Также обратите внимание:
Ключи, используемые в массивах, должны быть уникальными среди их братьев и сестер. Однако они не должны быть глобально уникальными.
Однако реальный ответ на ваш вопрос живет здесь: https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
Есть много библиотек, которые генерируют случайные уникальные идентификаторы, такие как Shtitid или UUID (который является самым популярным, просто посмотрите на количество загрузок), или просто создайте свою собственную функцию, которая генерирует случайные строки.
Вы можете добавить их непосредственно в объект в массиве
const todos = [
{
id: uuid(),
text: 'foo',
}
]
и повторить так:
const todoItems = todos.map(({id, text}) =>
<li key={id}>
{text}
</li>
);
Вы можете использовать Date.now() с индексом, ваш код будет как Ex.
const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];
render(){
const items = data.map((item, i) => {
let keyValue = Date.now()+i;
return <span key={keyValue}>{item.name}</span>;
}
return(
<div>
{items}
</div>
)
}