如何为React元素创建唯一键?


74

我正在制作一个React应用程序,该应用程序允许您创建列表并保存它,但是React一直警告我,我的元素没有唯一的键道具(元素List / ListForm)。如何为用户创建的元素创建唯一的键道具?下面是我的React代码

var TitleForm = React.createClass({
    handleSubmit: function(e) {
        e.preventDefault();
        var listName = {'name':this.refs.listName.value};
        this.props.handleCreate(listName);
        this.refs.listName.value = "";
    },
    render: function() {
        return (
            <div>
                <form onSubmit={this.handleSubmit}>
                    <input className='form-control list-input' type='text' ref='listName' placeholder="List Name"/>
                    <br/>
                    <button className="btn btn-primary" type="submit">Create</button>
                </form>
            </div>
        );
    }
});

var ListForm = React.createClass({
    getInitialState: function() {
        return {items:[{'name':'item1'}],itemCount:1};
    },
    handleSubmit: function(e) {
        e.preventDefault();
        var list = {'name': this.props.name, 'data':[]};
        var items = this.state.items;
        for (var i = 1; i < items.length; i++) {
            list.data.push(this.refs[items[i].name]);
        }
        this.props.update(list);
        $('#'+this.props.name).remove();
    }, 
    handleClick: function() {
        this.setState({
            items: this.state.items.concat({'name':'item'+this.state.itemCount+1}),
            itemCount: this.state.itemCount+1
        });
    },
    handleDelete: function() {
        this.setState({
            itemCount: this.state.itemCount-1
        });
    },
    render: function() {
        var listItems = this.state.items.map(function(item) {
            return (
                <div>
                    <input type="text" className="list-form" placeholder="List Item" ref={item.name}/>
                    <br/>
                </div>
            );
        });
        return (
            <div>
                <form onSubmit={this.handleSubmit} className="well list-form-container">
                    {listItems}
                    <br/>
                    <div onClick={this.handleClick} className="btn btn-primary list-button">Add</div>
                    <div onClick={this.handleDelete} className="btn btn-primary list-button">Delete</div>
                    <button type="submit" className="btn btn-primary list-button">Save</button>
                </form>
            </div>
        )
    }
});


var List = React.createClass({
    getInitialState: function() {
        return {lists:[], savedLists: []};
    },
    handleCreate: function(listName) {
        this.setState({
            lists: this.state.lists.concat(listName)
        });
    },
    updateSaved: function(list) {
        this.setState({
            savedLists: this.state.savedLists.concat(list)
        });
    },
    render: function() {
        var lst = this;
        var lists = this.state.lists.map(function(list) {
            return(
                <div>
                    <div key={list.name} id={list.name}>
                        <h2 key={"header"+list.name}>{list.name}</h2>
                        <ListForm update={lst.updateSaved} name={list.name}/>
                    </div>
                </div>
            )
        });
        var savedLists = this.state.savedLists.map(function(list) {
            var list_data = list.data;
            list_data.map(function(data) {
                return (
                    <li>{data}</li>
                )
            });
            return(
                <div>
                    <h2>{list.name}</h2>
                    <ul>
                        {list_data}
                    </ul>
                </div>
            )
        });
        var save_msg;
        if(savedLists.length == 0){
            save_msg = 'No Saved Lists';
        }else{
            save_msg = 'Saved Lists';
        }
        return (
            <div>
                <TitleForm handleCreate={this.handleCreate} />
                {lists}
                <h2>{save_msg}</h2>
                {savedLists}
            </div>
        )
    }
});

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

我的HTML:

<div class="container">
    <h1>Title</h1>
    <div id="app" class="center"></div>
</div>

2
您可以使用uuidnpm软件包。npmjs.com/package/uuid
RIYAJ KHAN,

@RIYAJKHAN-如果使用此软件包,则可以制作一个全局版本的const uuidv4 = require('uuid / v4'); 还是每个组件一个?
chobo2

@ chobo2没有类似的东西。您可以导入和使用它
RIYAJ KHAN '18

@RIYAJKHAN-我的意思是我应该将其放入自己的文件中,然后将其导出到其他组件中吗?但这听起来没有意义。
chobo2 '18

无需导出其他组件,只需导入您要使用它的组件
RIYAJ KHAN

Answers:


54

您可以通过多种方式创建unique keys,最简单的方法是在迭代数组时使用索引。

    var lists = this.state.lists.map(function(list, index) {
        return(
            <div key={index}>
                <div key={list.name} id={list.name}>
                    <h2 key={"header"+list.name}>{list.name}</h2>
                    <ListForm update={lst.updateSaved} name={list.name}/>
                </div>
            </div>
        )
    });

无论您将数据放在何处this.state.lists.map,都可以在此处将第二个参数传递function(list, index)给回调,这将是它的index值,并且对于数组中的所有项目都是唯一的。

然后你可以像这样使用它

<div key={index}>

您也可以在这里做同样的事情

    var savedLists = this.state.savedLists.map(function(list, index) {
        var list_data = list.data;
        list_data.map(function(data, index) {
            return (
                <li key={index}>{data}</li>
            )
        });
        return(
            <div key={index}>
                <h2>{list.name}</h2>
                <ul>
                    {list_data}
                </ul>
            </div>
        )
    });

编辑

但是,正如用户Martin Dawson在下面的评论中指出的那样,这并不总是理想的。

那么解决方案是什么?

许多

  • 您可以创建一个函数来生成唯一的键/ id /数字/字符串并使用
  • 您可以使用现有的npm软件包,例如uuiduniqid
  • 您还可以生成类似的随机数,new Date().getTime();并在要迭代的项中添加一些前缀,以保证其唯一性
  • 最后,我建议使用从数据库获得的唯一ID(如果获得)。

例:

const generateKey = (pre) => {
    return `${ pre }_${ new Date().getTime() }`;
}

const savedLists = this.state.savedLists.map( list => {
    const list_data = list.data.map( data => <li key={ generateKey(data) }>{ data }</li> );
    return(
        <div key={ generateKey(list.name) }>
            <h2>{ list.name }</h2>
            <ul>
                { list_data }
            </ul>
        </div>
    )
});

81
这是好的,如果元素没有得到删除或添加到他们,但如果他们这样做,那么你将有奇怪的副作用是会作出反应的按键与错件关联
马丁·道森

3
只要看看这篇文章:codeburst.io/...
卡伦Kishmiryan

5
使用索引是一种反模式。只是不要这样做;会有一段时间它会出现问题,并且对其进行跟踪并不是一件很有趣的事情。更新*实际上其他人已经指出了这一点@jayqui
JustDave

3
为什么要在每个渲染器上生成唯一的ID?正如本文指出- stackoverflow.com/a/53980483/4664866,反应过来期待稳定的键
Antoan Elenkov

6
不要使用索引并且不要即时生成uuid-这是完全错误的。如果您遍历复杂的组件-动态生成密钥将导致组件和所有子组件的强制安装/卸载。请参阅上面的评论。答案中唯一正确的东西-静态(uuids例如来自数据库
-godblessstrawberry

42

重要的是要记住,React期望使用STABLE密钥,这意味着您应该分配一次密钥,并且列表中的每个项目每次都应接收相同的密钥,这样,React可以在协调虚拟DOM并决定哪些组件需要重新渲染。因此,如果您使用的是UUID,则需要在数据级别而不是UI级别执行。

还要记住,您可以将任何想要的字符串用作键,因此您通常可以将多个字段组合为一个唯一的ID,例如,${username}_${timestamp}可以作为聊天中一行的唯一键。


2
感谢您确认。我当时在做,key={uuid()}但注意到它在执行任何操作后更改了所有键,这使得所有元素都重新呈现。
蝙蝠侠

2
关于稳定键的要点,其他答案中未提及。
Antoan Elenkov '20

20

密钥可帮助React识别哪些项目已更改/添加/删除,并应提供给数组内的元素以赋予元素稳定的标识。

考虑到这一点,下面介绍了基本上三种不同的策略:

  1. 静态元素(当您不需要保持html状态(焦点,光标位置等)时)
  2. 可编辑和可排序的元素
  3. 可编辑但不可排序的元素

正如React文档所解释的那样,我们需要为元素赋予稳定的标识,因此,请谨慎选择最适合您需求的策略:

静态元素

正如我们在React文档中还可以看到的那样,不建议对键使用索引“如果项目的顺序可能会更改。这会对性能产生负面影响,并可能导致组件状态出现问题”。

对于表格,列表等静态元素,我建议使用一个名为shortid的工具。

1)使用NPM / YARN安装软件包:

npm install shortid --save

2)导入要使用的类文件:

import shortid from 'shortid';

2)生成新ID的命令是shortid.generate()

3)范例

  renderDropdownItems = (): React.ReactNode => {
    const { data, isDisabled } = this.props;
    const { selectedValue } = this.state;
    const dropdownItems: Array<React.ReactNode> = [];

    if (data) {
      data.forEach(item => {
        dropdownItems.push(
          <option value={item.value} key={shortid.generate()}>
            {item.text}
          </option>
        );
      });
    }

    return (
      <select
        value={selectedValue}
        onChange={this.onSelectedItemChanged}
        disabled={isDisabled}
      >
        {dropdownItems}
      </select>
    );
  };

重要说明:由于React Virtual DOM依赖于键,因此每次重新渲染元素时都使用shortid,将创建一个新键,并且该元素将失去其html状态(如焦点或光标位置)。在决定如何生成密钥时,请考虑一下这一点,因为上述策略仅在构建不会更改其值(如列表或只读字段)的元素时才有用。

可编辑(可排序)字段

如果该元素是可排序的,并且您具有该项目的唯一ID,则将其与一些额外的字符串结合使用(以防您需要在页面中两次获得相同的信息)。这是最推荐的方案。

范例

  renderDropdownItems = (): React.ReactNode => {
    const elementKey:string = 'ddownitem_'; 
    const { data, isDisabled } = this.props;
    const { selectedValue } = this.state;
    const dropdownItems: Array<React.ReactNode> = [];

    if (data) {
      data.forEach(item => {
        dropdownItems.push(
          <option value={item.value} key={${elementKey}${item.id}}>
            {item.text}
          </option>
        );
      });
    }

    return (
      <select
        value={selectedValue}
        onChange={this.onSelectedItemChanged}
        disabled={isDisabled}
      >
        {dropdownItems}
      </select>
    );
  };

可编辑(不可排序)字段(例如输入元素)

作为最后的手段,对于输入之类的可编辑(但不可排序)字段,您可以使用一些带有一些起始文本的索引,因为元素键不能重复。

范例

  renderDropdownItems = (): React.ReactNode => {
    const elementKey:string = 'ddownitem_'; 
    const { data, isDisabled } = this.props;
    const { selectedValue } = this.state;
    const dropdownItems: Array<React.ReactNode> = [];

    if (data) {
      data.forEach((item:any index:number) => {
        dropdownItems.push(
          <option value={item.value} key={${elementKey}${index}}>
            {item.text}
          </option>
        );
      });
    }

    return (
      <select
        value={selectedValue}
        onChange={this.onSelectedItemChanged}
        disabled={isDisabled}
      >
        {dropdownItems}
      </select>
    );
  };

希望这可以帮助。


索引不是反模式,它是最后一种手段,如果您不提供键,则默认的响应方式会下降。它适用于已渲染但未重新排序或修改的稳定列表。
worc '19

你是对的@worc。谢谢。信息固定。干杯
Daniel Santana

9
这个示例当然可以工作,但是在每个渲染器上生成新的ID都不理想。这将导致所有子选项也重新呈现。对于复杂的组件,您将失去性能。最好尝试更改原始项目数据并附加这些生成的密钥,以便可以重复使用它们。这样可以防止不必要的渲染。
DiamondDrake '19

1
你是正确的钻石。感谢您的额外信息。另外,使用键的一个缺点是,例如,如果您正在编辑文本输入,则由于与您上述相同的原因,我们肯定会失去焦点。
丹尼尔·桑塔纳

1
“如果元素是可排序的,并且您具有该项目的唯一ID,则将其与一些额外的字符串结合使用(以防您需要在页面中两次获得相同的信息)”-为什么需要这样做?密钥只能在其兄弟姐妹之间唯一。检查react文档-reactjs.org/docs/lists-and-keys.html
Antoan Elenkov'Feb

10

不要使用这个return `${ pre }_${ new Date().getTime()}`;。最好使用数组索引而不是数组索引,因为即使它不理想,那样的话,您至少会在列表组件之间获得一定的一致性,而使用新的Date函数,您将获得恒定的不一致。这意味着该函数的每次新迭代都将导致一个新的真正唯一的键。

唯一键并不意味着它必须是全局唯一的,而是意味着它在组件的上下文中必须是唯一的,因此它不会一直无用地重新渲染。您最初不会感觉到与新Date相关的问题,但是会感觉到它,例如,如果您需要返回到已渲染的列表,而React开始感到困惑,因为它不知道哪个组件发生了更改以及哪个组件发生了更改否则,会导致内存泄漏,因为根据您的Date键,您猜到了每个组件都发生了更改。

现在我的答案。假设您要渲染一个YouTube视频列表。使用视频ID(arqTu9Ay4Ig)作为唯一ID。这样,如果该ID不变,则该组件将保持不变,但是如果保持不变,React将识别出它是一个新的Video并相应地对其进行更改。

不必那么严格,稍微放松一点的变体就是使用标题,例如Erez Hochman已经指出的,或者使用组件属性的组合(标题加类别),因此您可以告诉React检查是否已更改。

编辑了一些不重要的东西


0
const randomId = Math.round(Math.random() * 100000000)

想要的越随机,就越容易增加乘数。

但是,最好使用一些可信任的库来生成随机ID。


-1

使用UUID v4更新答案

npm install uuid
-------------------------------------
const uuidv4 = require('uuid/v4');
uuidv4(); // ⇨ '10ba038e-48da-487b-96e8-8d3b99b6d18a'

or

import uuidv from  'uuid/v4';
uuidv4(); // '10ba038e-48da-487b-96e8-8d3b99b6d18a

1
React的关键是避免不必要的渲染。生成uuid基本上会破坏React中密钥的目的。您最好使用map((value,index))中的索引
特殊


-15

我正在使用这个:

<div key={+new Date() + Math.random()}>

由于键是在渲染时确定的,因此这将不起作用,因此无法确定。
steviejay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.