为组件提供唯一键时,可以使用 Math.random() 来生成这些键吗?

When giving unique keys to components, is it okay to use Math.random() for generating those keys?

问题如下:

我有几千个元素列表形式的数据。其中一些是重复的,因此也可能有重复的密钥。 因为我没有真正的 "ID" 或任何可以让我有机会将所有元素的 id 作为唯一键的东西,所以可以使用 Math.random() 代替吗?

据我了解,key主要是react用来区分组件的。我认为就我与代码中的键没有任何关系而言,这应该没问题吗?为了确保不会有重复的数字,我不妨将两个数学随机数相互划分以获得几乎肯定唯一的密钥。

这是一个好习惯吗?我可以使用它而不必担心任何事情吗?

每次组件的键更改时,React 都会 create a new component instance rather than update the current one,因此为了性能起见,至少可以说使用 Math.random() 不是最佳选择。

此外,如果您要以任何方式重新排序组件列表,使用索引作为键 也不会 有帮助,因为 React 协调器将无法移动围绕与组件关联的现有 DOM 节点,相反,它必须为每个列表项重新创建 DOM 节点,这将再次具有次优性能。

但重申一下,如果您要重新排序列表,这只会是一个问题,所以在您的情况下,如果您确定不会重新排序您的列表 无论如何,你可以安全地使用索引作为键。

但是,如果您确实打算对列表重新排序(或者只是为了安全起见),那么我会为您的实体生成唯一 ID - 如果没有您可以使用的预先存在的唯一标识符。

添加 ID 的一种快速方法是仅映射列表并将索引分配给每个项目当您首次收到(或创建)列表时。

const myItemsWithIds = myItems.map((item, index) => { ...item, myId: index });

这样每个项目都会获得一个唯一的静态 ID。

tl;dr 如何为找到此答案的新手选择密钥

  1. 如果您的列表项具有唯一 ID(或其他唯一属性) 将其用作密钥

  2. 如果您可以组合列表项中的属性来创建唯一值,请使用该组合作为键

  3. 如果 none 上面的工作,但你可以小指保证你不会以任何方式重新排序你的列表,你可以使用数组索引作为键,但你是第一次收到或创建列表时,最好将您自己的 ID 添加到列表项中(见上文)

来自react documentation

Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

密钥应该稳定、可预测且唯一。不稳定的键(如 Math.random() 生成的键)将导致不必要地重新创建许多组件实例和 DOM 节点,这可能导致子组件中的性能下降和丢失状态。

https://facebook.github.io/react/docs/reconciliation.html

只需在您的 React 组件中实现以下代码...

constructor( props ) {
    super( props );

    this.keyCount = 0;
    this.getKey = this.getKey.bind(this);
}

getKey(){
    return this.keyCount++;
}

...并在每次需要新密钥时调用 this.getKey(),例如:

key={this.getKey()}

Is this a good practice? Can I use this without having to worry about anything?

没有也没有

Keys should be stable, predictable, and unique. Unstable keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

让我用 simple example 来说明这一点。

class Input extends React.Component {
  handleChange = (e) =>  this.props.onChange({
    value: e.target.value,
    index: this.props.index
  });
  render() {
    return (
      <input value={this.props.value} onChange={this.handleChange}/>  
    )
  }
};

class TextInputs extends React.Component {
  state = {
    textArray: ['hi,','My','Name','is']
  };
  handleChange = ({value, index}) => {
    const {textArray} = this.state;
    textArray[index] = value;
    this.setState({textArray})
  };
  render(){
  return this.state.textArray.map((txt, i) => <Input onChange={this.handleChange} index={i} value={txt} key={Math.random()}/>)
  // using array's index is not as worse but this will also cause bugs.  
  // return this.state.textArray.map((txt, i) => <Input onChange={this.handleChange} index={i} value={txt} key={i}/>)                 
  };
};

为什么我不能在您要求的输入中输入多个字符?

这是因为我使用 Math.random() 作为关键道具映射多个文本输入。每次我输入一个字符时,onChange 道具都会触发并且 parent 组件的状态发生变化,从而导致 re-render。这意味着每次输入都会再次调用 Math.random,新的关键道具是 generated.So react renders new children Input components.In other words every time you type a character react 创建了一个新的 input 元素,因为 key 属性改变了。

阅读更多相关内容here

我认为为组件键提供 Math.random() 是不正确的,原因是当您生成随机数时,不能保证不会再次获得相同的数字。很有可能在渲染组件的时候再次生成相同的随机数,所以那一次就失败了。

有些人会争辩说,如果随机数范围更大,则不会再次生成数字的可能性很小。是的,正确,但您的代码可以随时生成警告。

一种快速的方法是使用 new Date(),它将是唯一的。

密钥应该稳定、可预测且唯一。 不稳定的键(如 Math.random() 生成的键)将导致许多组件实例和 DOM 节点被不必要地重新创建,这可能导致子组件的性能下降和丢失状态。

您应该为每个子项以及子项中的每个元素添加一个

这种方式 React 可以处理最小的 DOM 变化。

请通过https://reactjs.org/docs/reconciliation.html#recursing-on-children。在这里,您将通过代码示例获得最佳解释。

我有一个用例,我根据从远程 API 获取的数据渲染一个图块列表。例如,数据看起来像这样 -

[
  {referrals: 5, reward: 'Reward1'},
  {referrals: 10, reward: 'Reward2'},
  {referrals: 25, reward: 'Reward3'},
  {referrals: 50, reward: 'Reward4'}
]
  • 此列表可以在 client-side 上进行修改,其中列表中的随机条目(tile)可以是 spliced/removed,或者可以在列表末尾添加一个新条目(磁贴)。

  • 此列表中可能有重复条目,因此我无法根据列表条目的内容创建 hash/unique 键。

最初,我尝试使用数组索引作为渲染图块的键,但在这种情况下最终发生的情况是,如果我拼接索引 3 处的条目,则索引 4 在索引 3 处占据一席之地,对于 React,由于键 3 完好无损,因此在渲染时,它只是删除索引 4 处的图块,同时保持原始图块在index 3 仍然可见,这是不受欢迎的行为。

所以根据@josthoff 和@Markus-ipse 分享的上述想法,我使用了一个 client-side self-incrementing 计数器作为键(检查

最初从远程获取数据时 API,我向其添加了一个新的键属性

let _tiles = data.tiles.map((v: any) => (
  {...v, key: this.getKey()}
))

所以它看起来像下面

[
  {referrals: 5, reward: 'Reward1', key: 0},
  {referrals: 10, reward: 'Reward2', key: 1},
  {referrals: 25, reward: 'Reward3', key: 2},
  {referrals: 50, reward: 'Reward4', key: 3}
]

添加新条目(磁贴)时,我再次调用 this.getKey()。通过这样做,每个条目(tile)都有一个唯一的键,并且 React 的行为符合预期。

我本可以在这里使用随机十六进制密钥或 UUID 生成器,但为简单起见,我继续使用 self-incrementing 计数器。