【问题标题】:How to create unique keys for React elements?如何为 React 元素创建唯一键?
【发布时间】:2017-01-25 17:47:50
【问题描述】:

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

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>

【问题讨论】:

  • 你可以使用uuid npm 包。 npmjs.com/package/uuid
  • @RIYAJKHAN - 如果你使用这个包,你可以制作一个全局版本的 const uuidv4 = require('uuid/v4');还是每个组件都有一个?
  • @chobo2 没有什么比 .全局或本地组件。您可以导入和使用它
  • @RIYAJKHAN - 我的意思是我应该把它放在它自己的文件中,然后将它导出到其他组件中吗?但听起来这样做没有意义。
  • 不需要在其他组件中导出,在你要使用的组件中导入即可

标签: reactjs


【解决方案1】:

创建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 值,它将是唯一的对于数组中的所有项目。

然后你可以像这样使用它

&lt;div key={index}&gt;

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

    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>
    )
});

【讨论】:

  • 如果元素没有被删除或添加到它们中,这很好,但如果它们这样做了,那么你会产生奇怪的副作用,因为 React 会将键与错误的组件相关联
  • 看看这篇文章吧:codeburst.io/…
  • 使用索引是一种反模式。只是不要这样做;总会有一段时间它会出错,追踪它不会很有趣。更新*实际上已经有人指出了这一点@jayqui
  • 不要使用索引,也不要动态生成 uuid - 这是完全错误的。如果您迭代复杂的组件 - 动态生成密钥将导致组件和所有子组件的强制安装/卸载。见上面的 cmets。答案中唯一正确的 - 静态uuids 例如来自数据库
  • 生成随机 id 并不能解决问题。我们不应该在重新渲染之间更改 ids/keys。
【解决方案2】:

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

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

【讨论】:

  • 感谢您的确认。我正在做key={uuid()},但注意到它在任何使所有元素重新渲染的动作之后都更改了所有键。
  • 关于稳定密钥的好点,其他答案中没有提到。
  • 如果您有时间并且想要,请考虑在堆栈上写一篇文章或一个关于如何做到这一点的自答问题。许多人做错了,公认的答案做错了。这对社区非常有用
  • "因此,如果您使用 UUID,您需要在数据级别而不是 UI 级别进行操作。"考虑到这些“数据”可以是博客中的 cmets,或者 React 来自 fb,那么这在实践中是如何转化的,只是 cmets。我现在明白了,有时甚至 fb 都觉得有问题并“丢失”了一些 cmets。我的意思是什么时候元素被删除,在这种情况下,相关的 id 也应该被删除,并确保不要分配给其他东西
【解决方案3】:

Keys 帮助 React 识别哪些项目已更改/添加/删除,并应将其赋予数组内的元素以赋予元素稳定的身份。

考虑到这一点,基本上有以下三种不同的策略:

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

正如React Documentation 解释的那样,我们需要为元素提供稳定的身份,因此,请仔细选择最适合您需求的策略:

静态元素

我们也可以在 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 依赖于 key,因此每次重新渲染元素时使用 shortid 都会创建一个新 key,并且元素将失去其 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>
    );
  };

希望这会有所帮助。

【讨论】:

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

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

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

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

不必那么严格,稍微宽松一点的变体是使用标题,就像 Erez Hochman 已经指出的那样,或者是组件属性的组合(标题加类别),所以你可以知道做出反应以检查它们是否已更改。

编辑了一些不重要的东西

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 它就像一个魅力!谢谢!但是不知道有没有副作用什么的,有人吗?
    • 对我来说没有任何问题。谢谢。
    • 不起作用。孩子必须有钥匙。
    • @VinaySharma,它不适用于删除项目,它与使用索引相同。
    【解决方案6】:

    要添加 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>
    )
    

    【讨论】:

    • 这并不能解决当元素由于某种原因被删除然后键分配给其他东西的情况
    • 如果某些内容被删除,您不会想重复使用密钥。
    • 是的,你是对的,但不是那个意思。这完全取决于如何处理删除功能
    【解决方案7】:

    另一个选项是弱键:https://www.npmjs.com/package/weak-key

    import weakKey from "weak-key";
    
    const obj1 = {a : 42};
    const obj2 = {b : 123};
    const obj3 = {a : 42};
    
    console.log(weakKey(obj1)); // 'weak-key-1'
    console.log(weakKey(obj2)); // 'weak-key-2'
    console.log(weakKey(obj3)); // 'weak-key-3'
    
    console.log(weakKey(obj1)); // 'weak-key-1'
    

    【讨论】:

    • 这是解决问题的好方法。我正在寻找如何一起使用 sha1 + uuid5 从可变参数输入创建 uuid-s。这样,如果输入不变,uuid 将保持不变。并且组件会像这样获得它的键:sha1uuid('my-sub-entity', indexA, indexB)。
    【解决方案8】:

    对于一个简单的文本字符串数组;我正在尝试以下两种方法之一:

    1。 encodeURI 两者都可用; NodeJS 和浏览器

    
    const WithEncoder = () => {
      const getKey = useCallback((str, idx) => encodeURI(`${str},${idx}`), [])
    
      return (
        <div>
          {["foo", "bar"].map((str, idx) => (
            <div key={getKey(str, idx)}>{str}</div>
          ))}
        </div>
      )
    }
    
    

    2。 window.btoa 仅在浏览器中可用。

    const WithB2A = () => {
      const getKey = useCallback((str, idx) => window.btoa(`${str}-${idx}`), [])
    
      return (
        <div>
          {["foo", "bar"].map((str, idx) => (
            <div key={getKey(str, idx)}>{str}</div>
          ))}
        </div>
      )
    }
    

    【讨论】:

    【解决方案9】:

    视情况而定,当您只想渲染愚蠢的项目时,选择一个 uniqueId 创建者是可以的,但如果您渲染诸如拖放等项目并且您没有每个项目的任何 uniqueId,我建议在您的 redux 中重新映射该数据,映射器,无论在哪里,并为每个项目添加一个 uniqueId(而不是像

    重新映射后,您可以在组件中使用该新 ID。

    【讨论】:

      【解决方案10】:
      import React, {useState} from 'react';
      import {SafeAreaView,ScrollView,StyleSheet,Text,View,Dimensions} from 'react-native';
      const {width}=Dimensions.get('window');
      function sayfalar(){
        let pages=[]
        for (let i = 0; i < 100; i++) {    
          pages.push(<View key={i} style={styles.pages}><Text>{i}</Text></View>)
        }
         return pages
      }
      const App=()=>{
        return(
          
          <View style={styles.container}>
          <ScrollView horizontal={true} pagingEnabled={true}>
          {sayfalar()}
          
          </ScrollView>
          </View>
        )
      }
      const styles = StyleSheet.create({
        container:{
          flexDirection:'row',
          flex:1
        },
        pages:{
          width:width
        }
      })
      export default App;
      

      【讨论】:

      • 请不要转储纯代码答案。请记住,您不仅要回答 OP,还要回答未来的读者。特别是如果您自己是五年后的新读者。因此,请edit 回答包含关于为什么此代码回答手头问题的解释。欲了解更多信息,请拨打tour 并阅读How to Ask
      【解决方案11】:

      您可以使用react-html-id轻松生成uniq id:https://www.npmjs.com/package/react-html-id

      【讨论】:

        【解决方案12】:

        2021年最快的解决方案是使用uniqid:更多信息请转至https://www.npmjs.com/package/uniqid但总结一下:

        • 首先在您的终端和项目文件中:npm install uniqid
        • 在您的项目中导入 uniqid
        • 在您需要的任何键中使用它!
         uniqid = require('uniqid');
                        return(
                            <div>
                                <div key={ uniqid() } id={list.name}>
                                    <h2 key={ uniqid() }>{list.name}</h2>
                                    <ListForm update={lst.updateSaved} name={list.name}/>
                                </div>
                            </div>
                        )
                    });
        

        【讨论】:

          【解决方案13】:

          我正在使用这个:

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

          【讨论】:

          • 这不起作用,因为键是在渲染时确定的,因此不是确定性的。
          猜你喜欢
          • 2018-09-24
          • 1970-01-01
          • 1970-01-01
          • 2017-01-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多