简体   繁体   English

React:使用 useState 挂钩更新数组时出现问题

[英]React: issues updating array with useState hook

I have an array of objects representing the opening hours of a business, now the user is able to change the open and closing time of specific days and shifts, adding new shifts and removing them too, I'm still a newb at react and state hook with arrays and my changes are not reflected in my views.我有一组代表企业营业时间的对象,现在用户可以更改特定日期和班次的营业和关闭时间,添加新班次并删除它们,我仍然是 react 和 state 的新手与 arrays 挂钩,我的更改并未反映在我的观点中。

The actions I'm trying to do are:我正在尝试执行的操作是:

Updating a specific item property in array更新数组中的特定项目属性

openingTimes[dayIndex].hours[splitIndex].opens = selectedHour+':'+selectedMinute;

Removing last item of array删除数组的最后一项

openingTimes[i].hours.pop();

Adding an item to array将项目添加到数组

openingTimes[i].hours.push({ opens: 'Abre', closes: 'Cierra' });

And the openingTimes array of objects for reference:以及供参考的对象的 openingTimes 数组:

const [openingTimes, setOpeningTimes] = useState([
        {day:'monday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'tuesday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'wednesday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'thursday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'friday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'saturday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
        {day:'sunday', hours:    [ { opens: 'Abre', closes: 'Cierra' } ]},
    ]);

Updating state on complex data structures gets a little complex because of the immutability issue.由于不变性问题,在复杂数据结构上更新 state 会变得有点复杂。 As you're discovering, you can't just change the state object directly.正如您所发现的,您不能直接更改 state object。 You actually have to create a copy of the state, then mutate your copy, then call your setState with the new copy.您实际上必须创建 state 的副本,然后改变您的副本,然后使用新副本调用您的setState

But React's setState wants you to provide this new version of your state without changing or mutating the original version.但是 React 的setState希望你提供 state 的这个新版本,而不改变或改变原始版本。 This means that every array or object that contains a change needs to be shallow-copied, all the way from the top down to where you made the change.这意味着每个包含更改的数组或 object 都需要进行浅拷贝,从上到下一直到您进行更改的位置。 For a several level deep state like yours, this ends up looking something like this in vanilla JavaScript:对于像你这样的几级深度 state,这最终在香草 JavaScript 中看起来像这样:

// openingTimes[dayIndex].hours[splitIndex].opens = selectedHour+':'+selectedMinute;
// every object/array containing a change needs to be "cloned" to make it a new object/array.
setOpeningTimes(x => {
  const newState = [...x]; // clone array
  let nextLevel = x[dayIndex];
  newState[dayIndex] = { ...nextLevel }; // clone object
  nextLevel = nextLevel.hours;
  newState[dayIndex].hours = [...nextlevel]; // clone array
  newState[dayIndex].hours.opens = selectedHour + ':' + selectedMinute; // change string value
  return newState; // return iteratively cloned new state.
});

Fortunately there is an npm package calledimmutability-helper which does a reasonably good job of making it easier.幸运的是,有一个名为 immutability-helper的 npm package 可以很好地简化它。 (not easy, but easier). (不容易,但更容易)。 With immutability helper, your mutations would look like this:使用不变性助手,您的突变将如下所示:

import update from 'immutability-helper';
// ...

// openingTimes[dayIndex].hours[splitIndex].opens = selectedHour+':'+selectedMinute;
setOpeningTimes(x => update(x, {
  [dayIndex]: {
    hours: {
      [splitIndex]: {
        $set: selectedHour + ':' + selectedMinute
      }
    }
  }
}));

// openingTimes[i].hours.pop();
setOpeningTimes(x => update(x, { [i]: { hours: { $splice: [[x[i].hours.length-1, 1]] } } }));

// openingTimes[i].hours.push({ opens: 'Abre', closes: 'Cierra' });
setOpeningTimes(x => update(x, { [i]: { hours: { $push: { opens: 'Abre', closes: 'Cierra' } } } }));

Definitely a step in the right direction, but all those nested objects means you have a lot of braces to balance.绝对是朝着正确方向迈出的一步,但是所有这些嵌套对象意味着您有很多大括号需要平衡。 You have a limited selection of $ functions to work with.您可以使用有限的 $ 函数。 (eg you'll notice we had to use their $slice , since they don't have a $pop .) (例如,您会注意到我们必须使用他们的$slice ,因为他们没有$pop 。)

As an add-on to immutability-helper, I've often used this small function to simplify things, because all those nested objects in immutability-helper can get a little tedious IMHO.作为 immutability-helper 的附加组件,我经常使用这个小的 function 来简化事情,因为 immutability-helper 中的所有嵌套对象都会变得有点乏味恕我直言。 Using this "update2" helper, you can provide either a string or an array for your "path," and it will build the nested object structure for you.使用这个“update2”助手,你可以为你的“路径”提供一个字符串或一个数组,它会为你构建嵌套的 object 结构。

import update from 'immutability-helper';
// ...
const update2 = (state, path, value) => update(state, toObj(path, value));

function toObj(arr, value) {
  const obj = {}; let o = obj;
  if (typeof arr === 'string') arr = arr.split('.');
  const last = arr.pop();
  while (arr.length) {
    const key = arr.shift();
    if (!o[key]) o[key] = {};
    o = o[key];
  }
  o[last] = value;
  return obj;
}

This is what it looks like it practice.这就是它的做法。 I'll use an array for the first two paths, and then the last path I'll use the string notation.我将在前两条路径中使用一个数组,然后在最后一条路径中使用字符串表示法。

// openingTimes[dayIndex].hours[splitIndex].opens = selectedHour+':'+selectedMinute;
setOpeningTimes(x => update2(x, [dayIndex, 'hours' splitIndex, 'opens', '$set'], selectedHour+':'+selectedMinute));
// openingTimes[i].hours.pop();
setOpeningTimes(x => update2(x, [i, 'hours', '$splice'], [[x[i].hours.length-1, 1]]));
// openingTimes[i].hours.push({ opens: 'Abre', closes: 'Cierra' });
setOpeningTimes(x => update2(x, `${i}.hours.$push`, { opens: 'Abre', closes: 'Cierra' }));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM