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

IT技术 reactjs
2021-04-08 03:59:13

我正在制作一个 React 应用程序,它允许您创建一个列表并保存它,但是 React 一直在警告我我的元素没有唯一的键属性(元素 List/ListForm)。我应该如何为用户创建的元素创建一个唯一的键props?下面是我的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>
6个回答

有很多方法可以创建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>
    )
});
看看这篇文章:codeburst.io/...
2021-05-22 03:59:13
使用索引是一种反模式。不要这样做;会有一段时间它会出错,追踪它不会很有趣。更新*实际上已经有人指出了这一点@jayqui
2021-05-24 03:59:13
如果元素没有被删除或添加到它们中,这很好,但是如果它们这样做了,那么您将产生奇怪的副作用,因为 React 会将键与错误的组件相关联
2021-06-12 03:59:13
生成随机 ID 并不能解决问题。我们不应该在重新渲染之间更改 ids/keys。
2021-06-14 03:59:13
不要使用索引,也不要即时生成 uuid - 这是完全错误的如果您迭代复杂的组件 - 动态生成密钥将导致组件和所有子项的强制挂载/卸载。见上面的评论。答案中唯一正确的东西 - 静态的,uuids例如来自数据库
2021-06-18 03:59:13

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

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

感谢您确认这一点。我正在做key={uuid()}但注意到它在任何操作后更改了所有键,这使所有元素重新呈现。
2021-06-12 03:59:13
“因此,如果您使用 UUID,则需要在数据级别而不是 UI 级别执行此操作。” 考虑到这些“数据”可以是博客中的评论,或者作为 React 来自 fb,只是评论,这在实践中如何转化。我现在明白有时即使是 fb 也觉得有问题并“丢失”了一些评论。我的意思是什么时候元素被删除,在这种情况下,相关的 id 也应该被删除,并确保不要被分配给其他东西
2021-06-15 03:59:13
如果您有时间并且想这样做,请考虑在堆栈上写一篇文章或自答题,了解如何做到这一点。许多人做错了,公认的答案做错了。这对社区非常有用
2021-06-17 03:59:13
关于稳定键的好点,其他答案中没有提到。
2021-06-18 03:59:13

Keys 帮助 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>
    );
  };

希望这可以帮助。

你是对的钻石。感谢您提供额外信息。此外,使用键的缺点是,例如,如果您正在编辑文本输入,我们肯定会因为您上面提到的相同原因而失去焦点。
2021-05-25 03:59:13
@Antoan,你是对的但是正如你指出的那样,“......如果你需要在一个页面中两次获得相同的信息”,这意味着如果你有任何原因列表呈现两次,那么你需要制作唯一的钥匙。干杯。
2021-05-28 03:59:13
“如果该元素是可排序的,并且您拥有该项目的唯一 ID,请将其与一些额外的字符串组合在一起(以防您需要在页面中两次使用相同的信息)” - 为什么需要这样做?Key 必须仅在其兄弟中唯一。检查反应文档 - reactjs.org/docs/lists-and-keys.html
2021-05-29 03:59:13
index 不是反模式,它是最后的工具,如果您不提供密钥,则反应会退回到默认值。它适用于呈现的稳定列表,但不会重新排序或修改。
2021-06-03 03:59:13
这个例子当然有效,但在每次渲染时生成新 ID 并不理想。它会导致所有子选项也被重新渲染。在复杂组件的情况下,您将失去性能。最好尝试更改原始项目数据并附加这些生成的密钥,以便它们可以重复使用。这将防止不必要的渲染。
2021-06-21 03:59:13

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

唯一键并不意味着它需要是全局唯一的,它意味着它需要在组件的上下文中是唯一的,因此它不会一直运行无用的重新渲染。一开始你不会感觉到与 new Date 相关的问题,但是你会感觉到,例如,如果你需要回到已经渲染的列表并且 React 开始变得很困惑,因为它不知道哪个组件发生了变化,哪个组件发生了变化没有,导致内存泄漏,因为,你猜对了,根据你的 Date 键,每个组件都改变了。

现在我的回答。假设您正在呈现 YouTube 视频列表。使用视频 ID (arqTu9Ay4Ig) 作为唯一 ID。这样,如果 ID 没有改变,组件将保持不变,但如果改变,React 会识别它是一个新的 Video 并相应地更改它。

不必那么严格,稍微宽松一点的变体是使用标题,就像 Erez Hochman 已经指出的那样,或者是组件属性的组合(标题加类别),这样你就可以告诉 React 检查如果他们改变了。

编辑了一些不重要的东西

让 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"));
<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>

不工作。孩子必须有钥匙。
2021-06-07 03:59:13
它就像一个魅力!谢谢!但我不知道有没有副作用问题之类的,有人吗?
2021-06-09 03:59:13
@VinaySharma,它不适用于例如删除项目,它与使用索引相同。
2021-06-16 03:59:13
对我来说没有任何问题。谢谢。
2021-06-20 03:59:13