9

The significance of React keys - a visual explanation

 3 years ago
source link: https://dev.to/jtonzing/the-significance-of-react-keys---a-visual-explanation--56l7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Cover image for The significance of React keys - a visual explanation

The significance of React keys - a visual explanation

Feb 15, 2018

Updated on Feb 20, 2018

・4 min read

Disclaimer: This is a gross oversimplification and should be used as a basic guide on how reconciliation works, not a perfect example!

When handling arrays in React, utilisation of the 'key' attribute on each element can be crucial for avoiding needless rerender performance hits. This article will hopefully explain why you should always clearly define your keys, and what you are missing out on if you are not doing so.

Let us start with an array

const joshs = [{  Name: "Josh", }, { Name: "Joshina", }, {  Name: "Notjosh", }]
Enter fullscreen modeExit fullscreen mode

the business end of our React component which renders said array

<div>
    { joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
Enter fullscreen modeExit fullscreen mode

and the HTML it outputs

<div>
    <span key=’0’>Josh</span>
    <span key=’1’>Joshina</span>
    <span key=’3’>Notjosh</span>
</div>
Enter fullscreen modeExit fullscreen mode

Works great!

...but!

A new Josh has arrived, and he's told the others to move out of the way.

 [{ Name: "Mega Josh"}, {  Name: "Josh", }, { Name: "Joshina", }, {  Name: "Notjosh", }]
Enter fullscreen modeExit fullscreen mode

Our component receives the new list, does its thing...

<div>
    { joshs.map((person, index) => ( <span key={index}>{person.name}</span>)) }
</div>
Enter fullscreen modeExit fullscreen mode

and prints it out like it did before.

<div>
    <span key=’0’>Mega Josh</span>
    <span key=’1’>Josh</span>
    <span key=’2’>Joshina</span>
    <span key=’3’>Notjosh</span>
</div>
Enter fullscreen modeExit fullscreen mode

Works great!

...but!

Let us look under the hood on what's actually happening (in a very simplified fashion) with the process that React is going through when it renders its new list.

A React component, when you boil it down to its rawest form (i.e. convert it from JSX), is just an object with a set of properties. These properties define its type, name, state, what props it has received, whether it has children, etc.

Each time a change occurs in our array, a new list of Josh <span> component objects are created. The React reconciler will compare the newly created objects with the current versions it has in the DOM. If any differences are detected between certain properties, it will redraw the components believing that it's the same object, but properties have changed.

So with our example, we have our original array (of components), which can loosely be translated to something like this...

[{
  Type: "span",
  Key: "0",
  Children: "Josh"
}, {
  Type: "span",
  Key: "1",
  Children: "Joshina"
}, {
  Type: "span",
  Key: "2",
  Children: "Notjosh"
}]
Enter fullscreen modeExit fullscreen mode

The reconciler will look at the key, and the component properties (in our simplified case, the content or children), and then look through its previous list of components to see if it matches any previous combinations.

As our list uses the array index position as its key, when 'Mega Josh' arrives and shifts all the components down one position, every comparison now fails due to React noticing that the keys do not match their previous properties!

[{
  Type: "span",
  Key: "0",                // Expected 0 to match 'Josh'
  Children: "Mega Josh"     // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",
  Key: "1",                // Expected 1 to match 'Joshina'
  Children: "Josh"          // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",
  Key: "2",                // Expected 2 to match 'Notjosh'
  Children: "Joshina"       // IM DIFFERENT, REDRAW ME
}, {
  Type: "span",   
  Key: "3",                // IM NEW
  Children: "Notjosh"       // DRAW ME
}]
Enter fullscreen modeExit fullscreen mode

But! We can prevent this. If we clearly define a key which is static, unique, and uniquely associated with the properties it is related to, React can acknowledge that it's the same component, even when it has changed its position.

Let us rebuild our components with unique keys

n.b. In this example I use the name attribute to keep our josh objects simple, but this is not best practice as the likelihood of two components having the same key is quite high. Where possible you should always use some sort of primary key from the data object.

<div>
    { people.map((person, index) => ( <span key={`key-${person.name}`}>{person.name}</span>)) }
</div>
Enter fullscreen modeExit fullscreen mode

the exported HTML will now look like

<div>
    <span key=’key-Josh’>Josh</span>
    <span key=’key-Joshina’>Joshina</span>
    <span key=’key-Notjosh’>Notjosh</span>
</div>
Enter fullscreen modeExit fullscreen mode

and the updated array HTML

<div>
    <span key='key-Mega Josh'>Josh</span>
    <span key=’key-Josh’>Josh</span>
    <span key=’key-Joshina’>Joshina</span>
    <span key=’key-Notjosh’>Notjosh</span>
</div>
Enter fullscreen modeExit fullscreen mode

The keys are now unique to their data object (rather than their array position), so when we do our object comparison

[{
  Type: "span",
  Key: "key-Josh",
  Children: "Josh"
}, {
  Type: "span",
  Key: "key-Joshina",
  Children: "Joshina"
}, {
  Type: "span",
  Key: "key-Notjosh",
  Children: "Notjosh"
}]
Enter fullscreen modeExit fullscreen mode

the reconciler will see that some components haven't changed, they have simply moved, only the new component will be created and inserted at the front of the list, not affecting the components after it. Brilliant!

[{
  Type: "span",
  Key: "key-Mega Josh",    // IM NEW
  Children: "Mega Josh"     // DRAW ME
}, {
  Type: "span",
  Key: "key-Josh",         // Expected 'key-Josh' to match 'Josh'
  Children: "Josh"          // IM THE SAME, DONT REDRAW ME
}, {
  Type: "span",
  Key: "key-Joshina",      // Expected 'key-Joshina' to match 'Joshina'
  Children: "Joshina"       // IM THE SAME, DONT REDRAW ME
}, {
  Type: "span",
  Key: "key-Notjosh",      // Expected 'key-Notjosh' to match 'Notjosh'
  Children: "Notjosh"       // IM THE SAME, DONT REDRAW ME
}]
Enter fullscreen modeExit fullscreen mode

For some uses, the performance impact may be minimal (or even non-existant if the array never changes order). The benefits with adding keys will be noticeable with very large arrays that get sorted/re-ordered as it will eliminate the need for a majority of your list to be rendered. Magic!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK