Tenha cuidado ao iterar sobre matrizes !!
É um equívoco comum que o uso do índice do elemento na matriz seja uma maneira aceitável de suprimir o erro com o qual você provavelmente está familiarizado:
Each child in an array should have a unique "key" prop.
No entanto, em muitos casos, não é! Isso é antipadrão que, em algumas situações, pode levar a comportamentos indesejados .
Compreendendo o key
suporte
O React usa o key
suporte para entender a relação do componente para o elemento DOM, que é usada para o processo de reconciliação . Portanto, é muito importante que a chave permaneça sempre única ; caso contrário, há uma boa chance do React misturar os elementos e alterar o incorreto. Também é importante que essas chaves permaneçam estáticas durante todas as renderizações para manter o melhor desempenho.
Dito isto, nem sempre é necessário aplicar o acima, desde que se saiba que a matriz é completamente estática. No entanto, a aplicação de boas práticas é incentivada sempre que possível.
Um desenvolvedor do React disse nesta edição do GitHub :
- A chave não é realmente sobre desempenho, é mais sobre identidade (o que, por sua vez, leva a um melhor desempenho). valores atribuídos e alterados aleatoriamente não são identidade
- Não podemos fornecer chaves de forma realista [automaticamente] sem saber como seus dados são modelados. Eu sugeriria talvez usar algum tipo de função de hash se você não tiver ids
- Já temos chaves internas quando usamos matrizes, mas elas são o índice na matriz. Quando você insere um novo elemento, essas chaves estão erradas.
Em suma, um key
deve ser:
- Exclusivo - Uma chave não pode ser idêntica à de um componente irmão .
- Estático - Uma chave nunca deve mudar entre as renderizações.
Usando o key
suporte
Conforme a explicação acima, estude cuidadosamente as seguintes amostras e tente implementar, quando possível, a abordagem recomendada.
Ruim (potencialmente)
<tbody>
{rows.map((row, i) => {
return <ObjectRow key={i} />;
})}
</tbody>
Este é sem dúvida o erro mais comum visto ao iterar sobre uma matriz no React. Essa abordagem não é tecnicamente "errada" , é apenas ... "perigosa" se você não sabe o que está fazendo. Se você estiver iterando através de uma matriz estática, essa é uma abordagem perfeitamente válida (por exemplo, uma matriz de links no menu de navegação). No entanto, se você estiver adicionando, removendo, reordenando ou filtrando itens, precisará ter cuidado. Veja esta explicação detalhada na documentação oficial.
class MyApp extends React.Component {
constructor() {
super();
this.state = {
arr: ["Item 1"]
}
}
click = () => {
this.setState({
arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
});
}
render() {
return(
<div>
<button onClick={this.click}>Add</button>
<ul>
{this.state.arr.map(
(item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
)}
</ul>
</div>
);
}
}
const Item = (props) => {
return (
<li>
<label>{props.children}</label>
<input value={props.text} />
</li>
);
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Neste trecho, estamos usando uma matriz não estática e não estamos nos restringindo a usá-la como uma pilha. Essa é uma abordagem insegura (você verá o porquê). Observe como, à medida que adicionamos itens ao início da matriz (basicamente sem deslocamento), o valor de cada um <input>
permanece no local. Por quê? Porque o key
não identifica exclusivamente cada item.
Em outras palavras, a princípio Item 1
tem key={0}
. Quando adicionamos o segundo item, o item superior se torna Item 2
, seguido por Item 1
como o segundo item. No entanto, agora Item 1
tem key={1}
e não key={0}
mais. Em vez disso, Item 2
agora tem key={0}
!!
Como tal, o React acha que os <input>
elementos não foram alterados, porque a Item
chave with 0
está sempre no topo!
Então, por que essa abordagem às vezes é ruim?
Essa abordagem é arriscada apenas se a matriz for filtrada, reorganizada ou se os itens forem adicionados / removidos. Se é sempre estático, é perfeitamente seguro de usar. Por exemplo, um menu de navegação como ["Home", "Products", "Contact us"]
pode ser iterado com segurança por esse método, porque você provavelmente nunca adicionará novos links ou os reorganizará.
Em resumo, é aqui que você pode usar o índice com segurança como key
:
- A matriz é estática e nunca será alterada.
- A matriz nunca é filtrada (exibe um subconjunto da matriz).
- A matriz nunca é reordenada.
- A matriz é usada como uma pilha ou LIFO (última entrada, primeira saída). Em outras palavras, a adição só pode ser feita no final da matriz (por exemplo, push), e somente o último item pode ser removido (por exemplo, pop).
Se, no snippet acima, empurrássemos o item adicionado ao final da matriz, a ordem para cada item existente sempre estaria correta.
Muito mal
<tbody>
{rows.map((row) => {
return <ObjectRow key={Math.random()} />;
})}
</tbody>
Embora essa abordagem provavelmente garanta a exclusividade das chaves, ela sempre forçará a reagir para renderizar novamente cada item da lista, mesmo quando isso não for necessário. Essa é uma solução muito ruim, pois afeta muito o desempenho. Sem mencionar que não se pode excluir a possibilidade de uma colisão de chave no caso de Math.random()
produzir o mesmo número duas vezes.
Chaves instáveis (como as produzidas por Math.random()
) farão com que muitas instâncias de componentes e nós DOM sejam recriados desnecessariamente, o que pode causar degradação no desempenho e perda de estado nos componentes filhos.
Muito bom
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uniqueId} />;
})}
</tbody>
Essa é sem dúvida a melhor abordagem, pois usa uma propriedade exclusiva para cada item no conjunto de dados. Por exemplo, se rows
contiver dados buscados em um banco de dados, pode-se usar a Chave Primária da tabela ( que geralmente é um número de incremento automático ).
A melhor maneira de escolher uma chave é usar uma string que identifique exclusivamente um item da lista entre seus irmãos. Na maioria das vezes você usaria IDs dos seus dados como chaves
Boa
componentWillMount() {
let rows = this.props.rows.map(item => {
return {uid: SomeLibrary.generateUniqueID(), value: item};
});
}
...
<tbody>
{rows.map((row) => {
return <ObjectRow key={row.uid} />;
})}
</tbody>
Essa também é uma boa abordagem. Se o seu conjunto de dados não contiver nenhum dado que garanta exclusividade ( por exemplo, uma matriz de números arbitrários ), é possível que haja uma colisão de chave. Nesses casos, é melhor gerar manualmente um identificador exclusivo para cada item no conjunto de dados antes de iterá-lo. De preferência, ao montar o componente ou quando o conjunto de dados é recebido ( por exemplo, de props
ou de uma chamada de API assíncrona ), para fazer isso apenas uma vez , e não sempre que o componente é renderizado novamente. Já existem algumas bibliotecas disponíveis que podem fornecer essas chaves. Aqui está um exemplo: react-key-index .
key
propriedade exclusiva . Isso ajudará o ReactJS a encontrar referências aos nós DOM apropriados e atualizar apenas o conteúdo dentro da marcação, mas não a renderizar novamente a tabela / linha inteira.