如何为没有唯一ID的项目生成可预测的反应键?

时间:2017-11-20 22:48:55

标签: reactjs uuid

有时,我会在没有唯一ID的情况下映射集合。在这种情况下,react会将此警告记录到控制台:Each child in an array or iterator should have a unique "key" prop.为了解决此问题,我创建了以下es6模块。

const key = {
  count: 0,
  getKey: () => {
    key.count += 1;
    return key.count;
  },
};
export default key;

现在,我在整个React应用程序中导入key模块,并在需要唯一键时调用其getKey方法。但是,我不觉得像React建议的那样产生的密钥是可预测的。虽然第一个渲染可能会以可预测的方式映射具有键1-10的集合,但之后的任何渲染都将为该元素生成新键。使用像UUID这样的包会产生同样的效果吗?生成可预测的React密钥的正确方法是什么?我是否可以预测他们的意思?谢谢!

3 个答案:

答案 0 :(得分:2)

在渲染组件之前生成键是获得可预测组件的最佳方法。

在理想的世界中,您将使用已经拥有自己唯一ID的对象列表,在这种情况下,最好将这些ID用作键。

如果您要呈现可能包含重复项的数字列表,则可以在将它们传递给要呈现的组件之前为每个数字提供一个固定的键。

let numbers = [1, 1, 2, 3];

let numbersWithKeys = numbers.map(x => {
  return { value: x, key: key.getKey() };
});

function Numbers({ numbers }) {
  return (
    <ul>
     {numbers.map(number => <li key={number.key}>{number.value}</li>)}
    </ul>
  )
}

render(
  <Numbers numbers={numbersWithKeys} />
);

然后,您的组件每次呈现它都可以安全地为每个数字使用相同的密钥。您可以想象此组件呈现以下虚拟树:

<ul>
 <li key={1}>1</li>
 <li key={2}>1</li>
 <li key={3}>2</li>
 <li key={4}>3</li>
</ul>

<Numbers>组件可能会反转列表,并且您仍然会为每个项目使用相同的密钥。这就是React可预测的意思。

当您通过移动渲染的元素来更改列表的顺序时,React可以创建一些性能快捷方式,而不是重新渲染整个元素,但是如果您在渲染方法中生成键,那么它会看到每次都有全新的清单,并且必须重新渲染。

答案 1 :(得分:1)

这里有几件事要考虑:

  1. 唯一:键必须唯一,但仅适用于当前数组。
  2. 可预测:键必须一致地映射到同一元素。
  3. 键不必是令人费解的标识符。

可预测的是什么?如果我们以(index:key开始:1:1,2:2,3:3 并删除索引2上的元素,应保留:1:1,2:3。索引2中具有键2的项已被删除,索引3中的项已移至索引2,但保留了其键3。

最简单的方法当然是在数据中使用唯一属性,例如数据库ID(主键),但这并不总是可能的。假设您要创建一个动态表单,并带有可以添加或删除的输入字段。除了动态分配的唯一标识符之外,输入字段元素可能看起来完全相同。最简单的方法当然是使用map索引,但这是不可预测的,因为例如,如果删除三个字段中的第二个,它将改变。

我们有什么选择?

一种解决方案是跟踪状态下的元素。

class MyComponent extends Component {
    constructor() {
        super();

        this.state = {
            myArr: []
        };
    }

    _addHandler = el => {
        const arr = this.state[el];
        const length = arr.length;
        const lastId = length > 0 ? arr[length - 1] : 0;

        this.setState({ [el]: [...arr, lastId + 1] });
    };

    _removeHandler = (el, i) => {
        const newItems = [
            ...this.state[el].slice(0, i),
            ...this.state[el].slice(i + 1)
        ];

        this.setState({ [el]: newItems });
    };

    render() {
        const myList = this.state.myArr;

        return (
            <div>
                {myList.map((el, i) => (
                    <p>
                        {this.state.myArr[i]}{" "}
                        <span onClick={() => this._removeHandler("myArr", i)}>
                            remove
                        </span>
                    </p>
                ))}
                <p onClick={() => this._addHandler("myArr")}>Add One</p>
            </div>
        );
    }
}

您可以编写一个简单的包装器组件来使用此概念来跟踪数组中的元素,如果要做的事情很多。

要分配初始密钥,您可以在componentDidMount()

中执行以下操作
componentDidMount() {
    const {someArr} = this.props;
    // use the index to map the initial key values
    // after which use state to track the key pairings
    const newIds = someArr.map((el,i) => i+1);

    this.setState({myArr:newIds});
}

答案 2 :(得分:0)

您可以使用与该项目特别相关的任何数据...或(不推荐)您可以使用new Date().getTime()的随机ID key用于重用组件......为同一项提供相同的密钥非常重要

   [].map(item, index) => <Item key={'this_list_specific_name' + item.id} />