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

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

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>
268669 次浏览

有很多方法可以创建 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 在下面的评论中指出的,这并不总是理想的。

那解决办法是什么?

很多

  • 您可以创建一个函数来生成唯一的键/ids/number/string 并使用它
  • 您可以使用现有的 npm 包,如 Uuid独一无二
  • 您还可以生成随机数,比如 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>
)
});

我用的是这个:

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

键可以帮助 React 确定哪些项目已经更改/添加/删除,并且应该给予数组中的元素,以便为元素提供一个稳定的标识。

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

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

正如 反应文件解释的那样,我们需要给元素赋予稳定的身份,因此,谨慎地选择最适合您需要的策略:

静电元件

正如我们也可以在反应文档中看到的,不建议使用索引键“ 这可能会对性能产生负面影响,并可能导致组件状态出现问题”。

对于表、列表等静态元素,我建议使用 译自: 美国《科学》杂志网站(https://www.npmjs.com/package/short tid)原著: http://www.npmjs.com/package/short tid工具。

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

npm install shortid --save

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

import shortid from 'shortid';

2)生成新 id 的命令是 Create ()

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 依赖于键,因此每次重新呈现元素时都会创建一个新键,并且元素会失去它的 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>
);
};

希望这个能帮上忙。

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

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

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

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

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

它不必那么严格,稍微更轻松的变体是使用标题,就像 Erez Hochman 已经指出的,或者组件属性的组合(标题加分类) ,所以你可以告诉 React 检查它们是否已经改变。

编辑了一些不重要的东西

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

要添加2021年的最新解决方案..。

我发现 纳米机器人项目提供了唯一的字符串 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>
)

另一个选择是弱键: 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'
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;

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

给孩子分配钥匙

您可以利用 反应,孩子 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>

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

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

根据具体情况,当你只是想渲染一些愚蠢的项目时,选择一个 uniqueId 创建器是可以的,但是如果你渲染了一些项目,比如拖放等,而且你没有为每个项目添加 uniqueId,我建议在你的 redux,mapper 中重新映射这些数据,并为每个项目添加 uniqueId (而不是像 < Item key = { ... }这样的渲染) ,因为 React 不能在渲染之间执行任何检查(这样做有很多好处)。

通过重新映射,您可以在组件中使用新的 ID。

下面是我所做的,它的工作重新排序,添加,编辑和删除。一旦设置的关键是不改变,所以没有不必要的重新渲染。一个问题可能是一些显示停止: 它需要添加一个属性到您的对象在第一次渲染说“ _ reactKey”。

Psuedo TS 中的函数组件示例(即它不会在代码片段中运行) :

interface IRow{


myData: string,
_reactKey?:number
}




export default function List(props: {
rows: Array<IRow>
}) {
const {myRows} = props;
const [nextKey, setNextKey] = useState(100);
const [rows, setRows] = useState<Array<IRow>|undefined>();




useEffect(function () {
if (myRows) {
for (let row of myRows){
if (!row._reactKey){
row._reactKey = nextKey;
setNextKey(nextKey+1);
}
}
setRows(myRows);
} else if (!rows) {
setRows([]);
}
}, [myRows, columns]);
    

addRow(){
    

let newRow = { blah, blah, _reactKey : nextKey};
setNextKey(nextKey+1);
rows.push(newRow);
setRows({...rows});
    

}
    

function MyRow(props:{row:IRow}){
const {row} = props;
        

return <tr><td>{row._reactKey}</td><td>row.myData</td></tr>
    

}
    

return <table>
<tr><th>Index</th><th>React Key</th><th>My Data</th></tr>
rows.map((row, key)=>{
return <MyRow key={row._reactKey} row={row} />
}
</table>
        

}
            

    

    

我并不经常使用 response,但是上次我看到这个问题时,我只是创建了一个新的状态数组,并跟踪了那里的键。

const [keys, setKeys] = useState([0]);
const [items, setItems] = useState([value: "", key: 0,])

然后,当我向列表中添加一个新项时,我将从键数组中获取最后一个键,添加1,然后使用 setKeys 更新键数组。就像这样:

const addItemWithKey = () => {
// create a new array from the state variable
let newKeyArr = [...keys];
// create a new array from the state variable that needs to be tracked with keys
let newItemArr = [...items];
// get the last key value and add 1
let key = newKeyArr[newKeyArr.length-1] + 1;
newKeyArr.push(key);
newItemArr.push({value: "", key: key,});
// set the state variable
setKeys(newKeyArr);
setItems(newItemArr);
};

我不担心从键数组中删除值,因为它只用于在组件中迭代,我们试图解决从列表中删除项和/或添加新项的情况。通过从键数组中获取最后一个数字并添加一个,我们应该始终拥有唯一的键。