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

use*_*500 39 reactjs

我正在制作一个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'));
Run Code Online (Sandbox Code Playgroud)

我的HTML:

<div class="container">
    <h1>Title</h1>
    <div id="app" class="center"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

Dhr*_*Jha 36

您可以通过多种方式创建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>
        )
    });
Run Code Online (Sandbox Code Playgroud)

无论你在哪里砍掉数据,在这里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>
        )
    });
Run Code Online (Sandbox Code Playgroud)

编辑

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

那么解决方案呢?

许多

  • 您可以创建一个函数来生成唯一键/ ids /数字/字符串并使用它
  • 您可以使用现有的npm包,如uuid,uniqid
  • 您还可以生成随机数字,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>
    )
});
Run Code Online (Sandbox Code Playgroud)

  • 如果元素没有被删除或添加到它们中,这很好,但是如果这样做,那么你会产生奇怪的副作用,因为React会将键与错误的组件相关联 (58认同)
  • **不要使用索引并且不要动态生成 uuid - 这是完全错误的**。如果您迭代复杂的组件 - 动态生成密钥将导致组件和所有子组件的强制安装/卸载。请参阅上面的评论。答案中唯一正确的事情 - 静态“uuids”,例如来自数据库 (17认同)
  • 使用索引是一种反模式。只是不要这样做;总有一天,事情会出错,而追踪它并不有趣。更新* 实际上其他人已经指出了这一点@jayqui (7认同)
  • 看看这篇文章:https://codeburst.io/how-to-not-react-common-anti-patterns-and-gotchas-in-react-40141fe0dcd#76bd (4认同)
  • 为什么要在每次渲染时生成唯一的 ID?正如这里指出的 - /sf/answers/3778633841/,反应期望稳定的密钥 (4认同)
  • 生成随机 ID 并不能解决问题。我们不应该在重新渲染之间更改 id/key。 (3认同)
  • 这个接受答案不正确。正如上面评论中所指出的,每次元素渲染时都会生成随机键。这是一种反模式。React 使用键来检测它可能无法自动检测到的更改。键应该反映会触发 DOM 更改的日期。在提出的问题中,有一个名称属性和“updateSaved”,如果这些意味着它们在脸上显示的意思,这可能会成为更好的密钥。如果您使用 name 和 updateSaved 作为键,那么当这些更改时它将呈现。 (3认同)
  • 最初的答案应该被删除阅读:/sf/answers/3663912171/ (2认同)
  • 我们需要稳定的密钥 (2认同)

Ere*_*man 17

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

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

  • 感谢您确认这一点。我正在执行“key={uuid()}”,但注意到它在任何操作后更改了所有键,这使得所有元素都重新渲染。 (4认同)
  • 关于稳定键的好点,其他答案中没有提到。 (3认同)

Dan*_*ana 15

正如反应文档所解释的,不建议对键使用索引“ 如果项目的顺序可能会更改。这会对性能产生负面影响,并可能导致组件状态出现问题 ”。在这种情况下,我建议使用一种称为shortid的工具。

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

npm install shortid --save
Run Code Online (Sandbox Code Playgroud)

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

import shortid from 'shortid';
Run Code Online (Sandbox Code Playgroud)

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>
    );
  };
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助。我刚刚发布了这篇文章,因为很难找到这样的解决方案。

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

Vin*_*rma 10

让 React 为子项分配键

您可以利用React.Children API:

const { Children } = React;

const DATA = [
  'foo',
  'bar',
  'baz',
];

const MyComponent = () => (
  <div>
    {Children.toArray(DATA.map(data => <p>{data}</p>))}
  </div>
);


ReactDOM.render(<MyComponent />,document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

  • 它就像一个魅力!谢谢!但不知道有没有副作用什么的,有人知道吗? (4认同)
  • 不起作用。孩子必须有钥匙。 (3认同)
  • 这是行不通的。“key”属性需要是一个稳定的标识(即,不会为每个对象更改),并且“Children.toArray”确实会更改“key”。如果您有可以重新排序的对象,那么它将不起作用,并且您会遇到麻烦。 (2认同)

RIY*_*HAN 9

可以使用uuidnpm包.

Nodejs常用模块

const uuidv1 = require('uuid/v1');
uuidv1(); // '10ba038e-48da-487b-96e8-8d3b99b6d18a
Run Code Online (Sandbox Code Playgroud)

使用ecma6:

import uuidv1 from  'uuid/v1';
uuidv1(); // '10ba038e-48da-487b-96e8-8d3b99b6d18a
Run Code Online (Sandbox Code Playgroud)

  • 不要使用动态生成的ID用于React密钥.如果组件是动态呈现的,则会破坏密钥管理的目的,因为它会破坏整个树. (8认同)
  • @davidatthepark的原因是因为可能在每次重新渲染时重新生成密钥。所以发生的事情是,每次重新渲染密钥时,React都会创建该组件的新实例。与相同作用域中的所有其他组件相比,键应该在组件的整个生命周期中都可以使用,而不会因为唯一性而改变。如果可以满足这些要求,则可以使用动态键。有不同的方法,但是您可能无法分辨出这么简单。我建议使用Singleton来跟踪所有生成的密钥。 (2认同)

Dan*_*ger 9

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

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

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

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

编辑了一些不重要的东西


Dav*_*cia 8

添加 2021 年最新解决方案...

我发现该项目nanoid提供了独特的字符串 id,可以用作密钥,同时速度又快又非常小。

安装使用后npm install nanoid,使用如下:

import { nanoid } from 'nanoid';

// Have the id associated with the data.
const todos = [{id: nanoid(), text: 'first todo'}];

// Then later, it can be rendered using a stable id as the key.
const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
)
Run Code Online (Sandbox Code Playgroud)

  • 这并不能解决当元素因某种原因被删除然后将键分配给其他内容时的情况 (3认同)
  • 如果删除了某些内容,您将不想重复使用密钥。 (2认同)