React 密钥如何工作?

How does React key works?

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'))

当我设置key为item.id时,我设置了三个输入标签abc

当我点击反向时,组件 Li 将挂载,输入将反向

当我把key改成index,当我点击reverse时,组件li更新,输入标签不会改变,

我想知道这是怎么发生的?有没有人知道密钥是如何工作的?

React 使用 "key" 属性来确定是渲染组件的全新实例,还是更新现有组件。因此,使用项目 ID 作为键意味着该项目的组件不会被销毁和重新创建。如果您向列表中添加一个新项目,它将创建一个新组件,如果您删除一个项目,它将破坏旧的,但是如果您更新一个项目而不更改其 ID,该组件将只更新。

它对动态列表很有用,因为它减少了挂载组件从渲染一个列表项切换到渲染另一个列表项的奇怪情况,而实际上它应该是一个全新的组件实例。

正如@DuncanThacker 所解释的那样,key 用于标识唯一元素,因此在两次渲染之间,React 知道元素是新元素还是更新元素。请记住,React 会比较每个渲染以确定 DOM 中实际更改的内容。

现在解释您所看到的行为:

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

当您将 id 用作 key 时,您可以重新排序数组,但 React 只会在 第一次 时间创建和挂载节点。您在 componentWillUpdate 内输出 ===mount===,这就是为什么您会看到误导性输出,但 React 仅更新节点(根据需要移动它们)。内部 input 的状态也跟随每个 <Li> 组件到它的新位置,因为 React 正确地理解 <Li> moved,而不是简单地 re -绘制了不同的内容。

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

当您使用 index 作为 key 时,React 有效地将每个渲染通道视为以 相同顺序 渲染数组但具有不同的内容,因为每个无论数组内容的顺序如何,元素的 key 都是相同的 index。这也是为什么内部 input 保持在同一个位置,即使 val 标签呈现在不同的位置。 这正是您不应该将 index 用作 key 的原因。

你可以这样说明:

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

数组:

|-----------------------|---------------------|--------------------------|
| { 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 |
|-----------------------|---------------------|--------------------------|

index 渲染 key:

|-----------------------|---------------------|--------------------------|
| <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"   |
|-----------------------|---------------------|--------------------------|

item.id 渲染 key:

|-----------------------|---------------------|--------------------------|
| <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 |
|-----------------------|---------------------|--------------------------|

总结: 你应该总是使用 id 或其他一些唯一的元素标识符作为 key.

另请参阅:https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318