React 列表组件中 key 的作用

3,281 阅读3分钟

React 列表组件中 key 的作用是什么呢?下面通过分析列表组件中有 key 和 无 key 两个 demo 来弄明白列表组件中 key 的作用。

1. 列表组件中无 key

直接上代码:

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            list: ['a', 'b', 'c'],
        };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        const list = [...this.state.list];
        list.splice(1, 1);
        this.setState({list});
    }

    render() {
        const { list } = this.state;
        return (
            <Fragment>
                <ul>
                    {
                        list.map((item, index) => {
                            return (<li key={ index }>{ item }</li>);
                        })
                    }
                </ul>
                <button onClick={ this.handleClick }>Click</button>
            </Fragment>
        );
    }
}


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

代码思路很简单,当我点击按钮时,移除一个列表项。由于不指定显式的 key 值,那么 React 将默认使用索引 index 用作为列表项目的 key 值,所以用索引 index 作为 key 值,与组件列表中无 key 是一样的。下面来分析,当点击按钮后,React 所做的一系列事情:

  1. 当点击按钮后,执行 setState 方法,state.list 变为['a', 'c'];
  2. 执行 render 方法,生成新的虚拟 DOM 树,新旧虚拟 DOM 树如下图;
  3. 新的虚拟 DOM 对象和旧的虚拟 DOM 对象进行比较,React 列表项是通过相同的 key 值,找到对应的新旧虚拟 DOM 对象来比较,如下图,所以旧虚拟 DOM 'a' 对象会与新虚拟 DOM 'a' 对象比较,结果相同,真实 DOM 树中跟旧虚拟 DOM 'a' 对象对应的真实 DOM 节点不变,新虚拟 DOM 'c' 对象会与旧虚拟 DOM 'b' 对象比较,结果不同,真实 DOM 树跟旧虚拟 DOM 'b' 对象对应的真实 DOM 节点移除,并且在真实 DOM 树中创建跟新虚拟 DOM 'c' 对象对应的真实 DOM 节点,旧虚拟 DOM 树中的 'c' 对象在新虚拟 DOM 树中没有可比较的对象,真实 DOM 树移除跟旧虚拟 DOM 'c' 对应的真实 DOM 节点。总结起来就是,在真实 DOM 树进行 2 次移除 DOM 节点操作,1 次添加 DOM 节点操作。
  4. 根据真实 DOM 操作渲染页面。
    列表组件中无 key

2. 列表组件中有 key

直接上代码:

import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            list: ['a', 'b', 'c'],
        };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        const list = [...this.state.list];
        list.splice(1, 1);
        this.setState({list});
    }

    render() {
        const { list } = this.state;
        return (
            <Fragment>
                <ul>
                    {
                        list.map((item) => {
                            return (<li key={ item }>{ item }</li>);
                        })
                    }
                </ul>
                <button onClick={ this.handleClick }>Click</button>
            </Fragment>
        );
    }
}


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

这个 demo 与上面那个 demo 唯一的区别就是用列表项的数据作为 key 值,下面来分析,当点击按钮后,React 所做的一系列事情:

  1. 当点击按钮后,执行 setState 方法,state.list 变为['a', 'c'];
  2. 执行 render 方法,生成新的虚拟 DOM 树,新旧虚拟 DOM 树如下图;
  3. 新的虚拟 DOM 对象和旧的虚拟 DOM 对象进行比较,旧虚拟 DOM 'a' 对象会与新虚拟 DOM 'a' 对象比较,结果相同,真实 DOM 树中跟旧虚拟 DOM 'a' 对象对应的真实 DOM 节点不变,新虚拟 DOM 'c' 对象会与旧虚拟 DOM 'c' 对象比较,结果相同,真实 DOM 树中跟旧虚拟 DOM 'c' 对象对应的真实 DOM 节点不变,旧虚拟 DOM 树中的 'b' 对象在新虚拟 DOM 树中没有可比较的对象,真实 DOM 树移除跟旧虚拟 DOM 'b' 对应的真实 DOM 节点。总结起来就是,在真实 DOM 树进行 1 次移除 DOM 节点操作。
  4. 根据真实 DOM 操作渲染页面。
    列表组件中无 key

总结

对比上面两个分析过程,可得出列表组件中有 key,并且 key 值不是索引,会减少多余的 DOM 操作,提高网页性能。DOM 操作是一件比较耗性能的事情,所以,在写列表组件时,最好以唯一标识符作为列表项的 key 值。