Home > OS >  Why can't React use a CONTENT to automatically generate keys?
Why can't React use a CONTENT to automatically generate keys?

Time:10-02

That's obvious that for the diffing algorithm in React keys are essential.
But I was wandering, why React just can't automatically generate keys based on the content we iterate over?

I also assume that items can share some similarity, or cab be identical in terms of content, but isn't it possible to generate keys once user open a page and somehow attach them to the items, so it is stable?

Or maybe there where attempts to solve the problem, if so, I would be grateful if you share it to me. Thank you!

CodePudding user response:

I believe, unless your data is 100% certainly going to sort in one way and never change, key={index} isn't a good key (which is what I assume you want your auto-generated keys to be). You'd ideally want something that is unique to each item, regardless of the order.

It's explained in more detail in the new beta react docs https://beta.reactjs.org/learn/rendering-lists#where-to-get-your-key

CodePudding user response:

React can't generate keys, because the entire point of keys is for you to help React track elements during it's tree-diffing stage.

For example, lets say you have the following code, where you naively use content instead of identifiers for your keys:

const people = usePeople(); // [{ id: "1", name: "James"}, {id: "2", name: "William"}]
return <ul>{people.map(p => <li key={p.name}>{p.name}</li>}</ul>

The above code will function and behave as you would expect. But what happens if the name of a person changes? To understand it, lets look at the tree it generates:

ul
  li(James) James
  li(William) William

If James becomes Josh between renders, the new tree will look like this:

ul
  li(Josh) Josh
  li(William) William

React will compare the two results and conclude the following:

  • li(James) is to be removed
  • li(Josh) is to be added

However, if we set our key prop to p.id, then the old and new tree will look as follows, respectively:

ul
  li(1) James
  li(2) William
ul
  li(1) Josh
  li(2) William

And when React compares the two, it will identify that James has become Josh, and needs only the text adjusted.

In the first scenario, the <li> component is completely destroyed, and a completely new component takes its place. Both of these actions run a complete React lifecycle for the component. In the second, it remains untouched, and only the text inside changes.

While in this contrived scenario, the performance penalty in the first case in minimal, it may be very significant with complex components.

CodePudding user response:

I think what you are implying is React could potentially choose to use something like a stable hash (say sha1 on a serialised string or something) on the object to generate a unique key. I think this actually would work in many cases, and even gave me pause for thought for a while! Your question is actually a really good one, and a deep one.

However, it wouldn't work in every case. I think it would work only on a static object which has no methods or anything attached. On a JS object, not all properties are enumerable. Hashing something could only ever happen on the enumerable objects of properties, but the dev may have non-enumerable yet-still-unique methods attached to these objects. In fact, even enumerable methods cant really be serialised reliably and they could be what makes the object unique. Not to mention the complexities of reliably hashing something with prototypical inheritance involved.

I suspect theres also a performance aspect to this. Hashing is cheap, but no that cheap. Most cases can be keyed by just referencing a unique ID in the object, which is vastly cheaper. When enumerating a very large number of objects, these things matter, and so its better to defer to the developer. Afterall, if you really do want to hash it, its just one function call in userland -- and this saves great confusion on developer side when it doesnt work. The Principle of least astonishment comes to mind.

Theres also an aspect of how this would limit the power of how expressive JSX can be due to it basically allowing freeform JS. You would probably have to supply some low level <React.Map> component primitives in order for react to supply this default key handling which implies you are a bit more restrained on what you can and cant do (complex functional chains).

  • Related