How does React key works?

2.5k views Asked by At

import React from 'react'
import ReactDOM from 'react-dom'

class App extends React.Component{
   constructor(props) {
      super(props)
      this.state = {
         list: [{id: 1,val: 'aa'}, {id: 2, val: 'bb'}, {id: 3, val: 'cc'}]
      }
   }

   click() {
      this.state.list.reverse()
      this.setState({})
   }

   render() {
      return (
            <ul>
                <div onClick={this.click.bind(this)}>reverse</div>
                {
                 this.state.list.map(function(item, index) {
                  return (
                            <Li key={item.id} val={item.val}></Li>
                  )
                 }.bind(this))
                }
            </ul>
      )
   }
}

class Li extends React.Component{
   constructor(props) {
      super(props)
   }
   componentDidMount() {
      console.log('===did===')
   }
   componentWillUpdate(nextProps, nextState) {
      console.log('===mount====')
   }
   render() {
      return (
            <li>
                {this.props.val}
                <input type="text"></input>
            </li>
      )
   }
}

ReactDOM.render(<App/>, document.getElementById('app'))

when I set key as item.id, and I set three input tags a, b, c;

when I click reverse, the component Li will mount, input will reverse

when I change key as index, when I click reverse, the component Li update, and the input tag will not change,

I want to know how does it happen? Does Anyone have found out how key works?

2

There are 2 answers

0
Aaron Beall On BEST ANSWER

As @DuncanThacker explained the key is used to identify unique elements, so that between 2 render passes React knows if an element is a new thing or an updated thing. Remember that React diffs each render to determine what actually changed in the DOM.

Now to explain the behavior you're seeing:

when I set key as item.id, and I set three input tags a, b, c;

when I click reverse, the component Li will mount, input will reverse

When you use id as key you can re-order the array but React will only create and mount the node the first time. You are outputting ===mount=== inside componentWillUpdate, which is why you see that misleading output, but React is only updating the nodes (moving them as needed). The state of the inner input also follows each <Li> component to its new position, since React correctly understands that the <Li> moved, not simply re-drawn with different content.

when I change key as index, when I click reverse, the component Li update, and the input tag will not change

When you use index as key React effectively sees each render pass as rendering the array in the same order but with different content, since each element's key is the same index no matter what order the array content is in. This is also why the inner input stays in the same place, even though the val label is rendered in a different position. And this is exactly why you should not use index as key.

You could illustrate it like this:

|-----------------------|---------------------|--------------------------|
|        Before         |        After        |          Change          |
|-----------------------|---------------------|--------------------------|

Array:

|-----------------------|---------------------|--------------------------|
| { id: 1, val: "A" }   | { id: 3, val: "C" } | Moved from last to first |
| { id: 2, val: "B" }   | { id: 2, val: "B" } | None                     |
| { id: 3, val: "C" }   | { id: 1, val: "A" } | Moved from first to last |
|-----------------------|---------------------|--------------------------|

Render key from index:

|-----------------------|---------------------|--------------------------|
| <Li key=0 val="A">    | <Li key=0 val="C">  | Val changed "A" to "C"   |
| <Li key=1 val="B">    | <Li key=1 val="B">  | None                     |
| <Li key=2 val="C">    | <Li key=2 val="A">  | Val changed "C" to "A"   |
|-----------------------|---------------------|--------------------------|

Render key from item.id:

|-----------------------|---------------------|--------------------------|
| <Li key=1 val="A">    | <Li key=3 val="C">  | Moved from bottom to top |
| <Li key=2 val="B">    | <Li key=2 val="B">  | None                     |
| <Li key=3 val="C">    | <Li key=1 val="A">  | Moved from top to bottom |
|-----------------------|---------------------|--------------------------|

Summary: you should always use id or some other unique element identifier as key.

See also: https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

0
Duncan Thacker On

The "key" attribute is used by React to determine whether to render a brand new instance of the component, or whether the update an existing one. So using the item id as the key will mean that the component for that item will not be destroyed and re-created. If you add a new item to the list it will create a new component, and if you remove an item it will destroy the old one, but if you update an item without change its ID, the component will just update.

It's useful for dynamic lists because it reduces weird cases where a mounted component switches from rendering one list item to rendering another list item, when really it should be a brand new component instance.