React-更新状态数组时对象中的唯一键被覆盖

时间:2019-08-13 06:04:17

标签: javascript reactjs multidimensional-array reference javascript-objects

我有一个将商品添加到购物车的功能。我试图在单击按钮时为新对象项生成唯一键。我有new Date().getTime()根据点击频率进行此操作。我在函数内部测试了console.log,它为每个新对象生成不同的unique_id号。当我尝试将对象的现有状态数组与新的item对象结合在一起时,问题就开始了。现有的所有unique_id都将覆盖为最新的unique_id。我也在状态数组中使用React useState钩子。 React有点相关,因为状态数组不应直接编辑,而应通过setter方法进行编辑。

我尝试了.push.concatArray.from,扩展运算符,在函数内部和外部循环和分配变量的组合。我知道这是pass-by-referencepass-by-value的局面,因为这些数组方法只是浅表副本。

const [cart, setCart] = useState([])

const addItem = (item) => {
   let cartArr = cart
   item['unique_id'] = new Date().getTime()
   setCart([...cartArr, item])
}

预期:

[
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696123},
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696256},
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696377}
]

但得到:

[
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696377},
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696377},
{id: 1, price: 10.11, title: "The Art Of War", unique_id: 1565675696377}
]

6 个答案:

答案 0 :(得分:1)

代替在数组中推送值,您可以连接数组和对象,请参见下面的代码供您参考

  const [cart, setCart] = useState([]);
  const addItem = item => {
    item["unique_id"] = new Date().getTime();
    setCart([...cart, item]);
  };

这是codeandbox的link

答案 1 :(得分:0)

正如我在评论中提到的,这取决于您呼叫addItem的频率。 下面的代码片段显示了如果您连续调用addItem,所有项目都会(几乎总是)获得相同的时间:

let cart = [];

const addItem = (item) => {
    let cartArr = cart;
    let currentTime = new Date().getTime();
    item['unique_id'] = currentTime;
    cartArr.push(item);
    cart = cartArr;
}
addItem({ price: 10 });
addItem({ price: 11 });
addItem({ price: 12 });
console.log(cart);

但是,在通过引入console.log添加一些延迟之后,时间戳是不同的(不保证):

let cart = [];

const addItem = (item) => {
    let cartArr = cart;
    let currentTime = new Date().getTime();
    console.log('Price: ' + item.price);
    console.log('Current time: ' + currentTime);
    item['unique_id'] = currentTime;
    cartArr.push(item);
    cart = cartArr;
}
addItem({ price: 10 });
addItem({ price: 11 });
addItem({ price: 12 });
console.log(cart);

所以,在这里,我认为React状态与时间戳或数组操作无关。

答案 2 :(得分:0)

正如@fiveelements所说,由于Date.now()并不是生成随机值的可靠方法,因此会产生id冲突。幸运的是,浏览器具有恰好可以生成随机值的新功能。您可以尝试将其用作ID:

const id = window.crypto.getRandomValues(new Uint32Array(10))[0];
console.log(id);

您可以阅读有关window.crypto object on MDN的更多信息。所有现代浏览器都支持该功能,包括IE11:https://caniuse.com/#search=crypto

如果您是从数据库之类的地方获取数据的,则可能已经有了某种可以使用的唯一ID。如果您可以使key在渲染之间保持一致,则可以避免不必要的重新渲染,从而提高性能。但是,如果您没有任何ID,此代码段可以帮助您即时生成它们。

您也可以选中此CodePen

答案 3 :(得分:0)

该问题与React无关。执行的循环恰好在同一时间戳下完成。

我建议不要模拟通过循环在购物车中添加多个物品。

var arr = [];

for(let i = 0; i < 10; i++){
  arr.push(new Date().getTime())
}

console.log(arr);

答案 4 :(得分:0)

到目前为止,您可能一直都想不出来,时间戳很烂,无法做出反应作为键。

重要-React希望使用 STABLE 键,键分配应该完成一次,并且每个项目每次都应获得相同的键,以便React可以发挥其创造价值的魔力更改并决定何时重新渲染。 但是new Date函数永远不会为您提供STABLE密钥,始终不一致。函数的每个新值都会赋予唯一键。 唯一的应该是有问题的组件,而不是全局的应用程序,以避免重新渲染该组件。

始终最好使用数据库中的id之类的标识符。

如果不可用 我建议使用shortid来生成密钥。

步骤:-

1)npm install shortid --save

2)import shortid from 'shortid';

3)用法

<li key={shortid.generate()}>
  {value}
</li>

希望有帮助!

答案 5 :(得分:0)

我用Object.assign()解决了这个问题。它还不需要额外的cartArr变量。

const [cart, setCart] = useState([])

const addItem = (item) => {
    item['unique_id'] = new Date().getTime()
    let newItem = Object.assign({}, item)
    setCart([...cart, newItem])
}