Why do we have a key

According to the official document of React, when recursing an array, if no other technology is available, the array will be compared from front to back based on the two trees, without any reuse logic. When we add an item to the front of the array, React will assume that each item has changed, causing each item to need to be updated. Sooner or later, this improper update will cause performance problems 😵.

After adding a key to each item in the array, React looks for the same key in the first and second trees to reuse. When react is given an appropriate key, react can reasonably determine what needs to be added or deleted.

Reactjs.org/docs/reconc…

How do I specify key

React uses a key as an attribute to determine whether components change. How can developers specify keys to optimize performance?

  1. Find an attribute in the array that is unique to each piece of data. For example, when rendering a list returned by the server, use id as the key. If the server does not provide it, we can design some concatenation to represent the key.

  2. The easiest way is to use the index of the array as the key.

After seeing these two ways of specifying keys, students with react skills will immediately realize that the first way is the best because the second way is problematic and is not recommended, balabala……

Yes, there is a problem with using the index of an array as a key. Today we will take you to discuss under what circumstances, there is a problem with using the index of an array as a key

Is index key 100% wrong 🧐?

Should we stick the index as a key? Let’s use a few real-world examples to see if this is true.

export default function App() {
  const [arr, setArr] = useState(
    Array(5)
      .fill("")
      .map((i, idx) => ({ id: idx, value: idx }))
  );

  const remove = useCallback(
    (index) => {
      setArr(arr.filter((i, idx) => idx !== index));
    },
    [arr]
  );

  const change = useCallback(
    (e, index) => {
      setArr(
        arr.map((i, idx) => {
          if (idx === index) {
            return {
              ...i,
              value: e.target.value
            };
          }
          return i;
        })
      );
    },
    [arr]
  );

  return (
    <div className="App">
      {arr.map((item, index) => {
        return (
          <div key={index}>
            <span style={{ marginRight: 10 }}>id:{item.id}</span>
            <span>输入框:</span>
            <Input
              value={item.value}
              onChange={(e) => change(e, index)}
            />
            <DeleteOutlined onClick={() => remove(index)} />
          </div>
        );
      })}
    </div>
  );
}
Copy the code

In the example of 👆, when we add items to the first and third fields and delete item 2, you can imagine what should happen to the interface.

  1. The id and input field on the second line are completely deleted, and then the next few lines are moved up;
  2. Again, the id on the second line is deleted, but the input field is the same as it was before, but the input field on the last line is deleted; From line 2 to the last line, the input field is not the same as before;

Duang, the actual result is!!

For another example, the difference between this example and the previous one is that we have changed the Input component from controlled to uncontrolled.

export default function App() { const [arr, setArr] = useState( Array(5) .fill("") .map((i, idx) => ({ id: idx, value: idx })) ); const remove = useCallback( (index) => { setArr(arr.filter((i, idx) => idx ! == index)); }, [arr] ); const change = useCallback( (e, index) => { setArr( arr.map((i, idx) => { if (idx === index) { return { ... i, value: e.target.value }; } return i; })); }, [arr] ); return ( <div className="App"> {arr.map((item, index) => { return ( <div key={index}> <span style={{ marginRight: 10}}>id:{item.id}</span> <span> Input defaultValue={item.value} onChange={(e) => change(e, e) index)} /> <DeleteOutlined onClick={() => remove(index)} /> </div> ); })} </div> ); }Copy the code

What happens if you repeat the previous step in the case of uncontrolled Input pricing? You can think about it

Controlled versus uncontrolled is the issue

In both cases, we used the index as the key, and the id on the left is always updated as needed. In the case of the Input component, the controlled Input component updates correctly, whereas in the uncontrolled Input component, all subsequent values are corrupted from where they were deleted. Next, we will analyze the difference from the perspective of how React updates.

reconciliation

People who know React know that when data changes, React performs dom diff to find the smallest changes and apply them to the real DOM. Key plays an important role in scenarios where nodes are generated by iterating through groups of numbers.

In the react virtual DOM, the original 5 items are represented by keys 0, 1, 2, 3, and 4. The new 4 items are represented by keys 0, 1, 2, and 3, respectively. React finds that key equals 4 is missing and removes it. The remaining four items can be reused, thus re-rendering the four items (in this case, the return values from the map function). And because the Input is in controlled mode at this time, the rendering content is completely controlled by the data, so the rendering result is normal.

Then come to the second 🌰. From the perspective of react, the key changes are consistent, deleting the last item and updating the first four items. Id :{{item.id}} does render normally according to the given ID, but the Input component is now in uncontrolled mode (data changes are not reflected in the UI).

For example, let’s take the data with id=3. Its key was 3 in the first rendering, but after deleting the second data, its key became 2. React will reuse key=2 and re-render it with {id:3,value:3}. However, key=2’s previous Input was 22222 and since it is in uncontrolled mode, it does not change the UI due to data changes. So we still see 22222, which results in an id:3 input field: 22222 that we didn’t expect.

So, how can we use a single value as a key without causing problems?

When react compares the old dom tree with the old one, the key is 0, 1, 2, 3, 4, and the key is 0, 2, 3, 4. React clearly knows that the key=1 is deleted, so it is deleted exactly. There is no need to modify other nodes and the final page is displayed correctly.

To summarize:

The reasons for this UI confusion are:

  1. The react update behavior is inconsistent with our actual operation because the index is used as the key. (Our subjective intention is to delete the component with ID =1, and we do not want any changes to other components.)
  2. During rendering with new data, there is a portion of the content whose UI presentation is unaffected by the data

conclusion

If the content we want to render is completely controlled by the data, then using the index as the key is also fine; But if we fail to do so, there will be unexpected consequences;