Javascript React:如何使用 setState 更新 state.item[1] 状态?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29537299/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 03:32:27  来源:igfitidea点击:

React: how to update state.item[1] in state using setState?

javascriptreactjsstate

提问by martins

I'm creating an app where the user can design his own form. E.g. specify name of the field and details of which other columns that should be included.

我正在创建一个应用程序,用户可以在其中设计自己的表单。例如,指定字段的名称以及应包括哪些其他列的详细信息。

The component is available as a JSFiddle here.

该组件可在此处作为 JSFiddle 使用。

My initial state looks like this:

我的初始状态如下所示:

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name', 
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key} 
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

I want to update the state when the user changes any of the values, but I'm having a hard time to target the correct object:

我想在用户更改任何值时更新状态,但我很难定位正确的对象:

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

How should I craft this.setStateto get it to update items[1].name?

我应该如何制作this.setState才能更新items[1].name

回答by Jonny Buchanan

You could use the updateimmutability helper for this:

您可以update为此使用不变性助手

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

Or if you don't care about being able to detect changes to this item in a shouldComponentUpdate()lifecycle method using ===, you could edit the state directly and force the component to re-render - this is effectively the same as @limelights' answer, as it's pulling an object out of state and editing it.

或者,如果您不关心能够使用 检测shouldComponentUpdate()生命周期方法中对此项目的更改===,则可以直接编辑状态并强制组件重新呈现 - 这实际上与@limelights 的答案相同,因为它是将对象拉出状态并对其进行编辑。

this.state.items[1].name = 'updated field name'
this.forceUpdate()


Post-edit addition:

后期编辑补充:

Check out the Simple Component Communicationlesson from react-trainingfor an example of how to pass a callback function from a state-holding parent to a child component which needs to trigger a state change.

查看react-training 中简单组件通信课程,了解如何将回调函数从保持状态的父组件传递到需要触发状态更改的子组件。

回答by mpen

Since there's a lot of misinformation in this thread, here's how you can do it without helper libs:

由于此线程中有很多错误信息,因此您可以在没有帮助程序库的情况下执行以下操作:

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

You can combine steps 2 and 3 if you want:

如果需要,您可以组合步骤 2 和 3:

let item = {
    ...items[1],
    name: 'newName'
}

Or you can do the whole thing in one line:

或者你可以在一行中完成整个事情:

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

Note: I made itemsan array. OP used an object. However, the concepts are the same.

注意:我做了items一个数组。OP 使用了一个对象。但是,概念是相同的。



You can see what's going on in your terminal/console:

您可以看到终端/控制台中发生了什么:

? node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied

回答by MarvinVK

Wrong way!

错误的方法!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

As pointed out by a lot of better developers in the comments: mutating the state is wrong!

正如许多更好的开发人员在评论中指出的那样:改变状态是错误的!

Took me a while to figure this out. Above works but it takes away the power of React. For example componentDidUpdatewill not see this as an update because it's modified directly.

我花了一段时间才弄清楚这一点。以上工作,但它带走了 React 的力量。例如,componentDidUpdate不会将此视为更新,因为它是直接修改的。

So the right waywould be:

所以正确的方法是:

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

回答by Neurotransmitter

To modify deeply nested objects/variables in React's state, typically three methods are used: vanilla JavaScript's Object.assign, immutability-helperand cloneDeepfrom Lodash.

为了在 React 状态中修改深度嵌套的对象/变量,通常使用三种方法:vanilla JavaScript's Object.assignimmutability -helpercloneDeep来自Lodash

There are also plenty of other less popular third-party libs to achieve this, but in this answer, I'll cover just these three options. Also, some additional vanilla JavaScript methods exist, like array spreading, (see @mpen's answer for example), but they are not very intuitive, easy to use and capable to handle all state manipulation situations.

还有很多其他不太流行的第三方库可以实现这一点,但在这个答案中,我将只介绍这三个选项。此外,还存在一些额外的原生 JavaScript 方法,例如数组传播(例如,请参阅 @mpen 的回答),但它们不是很直观、易于使用且无法处理所有状态操作情况。

As was pointed innumerable times in top voted comments to the answers, whose authors propose a direct mutation of state: just don't do that. This is a ubiquitous React anti-pattern, which will inevitably lead to unwanted consequences. Learn the right way.

正如在对答案的最高投票评论中无数次指出的那样,其作者提出了状态的直接突变:只是不要那样做。这是一种无处不在的 React 反模式,不可避免地会导致不良后果。学习正确的方法。

Let's compare three widely used methods.

让我们比较三种广泛使用的方法。

Given this state object structure:

鉴于此状态对象结构:

state = {
    outer: {
        inner: 'initial value'
    }
}

You can use the following methods to update the inner-most innerfield's value without affecting the rest of the state.

您可以使用以下方法更新最内层inner字段的值,而不会影响状态的其余部分。

1. Vanilla JavaScript's Object.assign

1. Vanilla JavaScript 的 Object.assign

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the shallow copying:', outer.inner) // initial value
    const newOuter = Object.assign({}, outer, { inner: 'updated value' })
    console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

<main id="react"></main>

Keep in mind, that Object.assignwill not perform a deep cloning, since it only copies property values, and that's why what it does is called a shallow copying(see comments).

请记住,Object.assign不会执行深度克隆,因为它只复制属性值,这就是为什么它所做的称为浅复制(见评论)。

For this to work, we should only manipulate the properties of primitivetypes (outer.inner), that is strings, numbers, booleans.

为此,我们应该只操作原始类型 ( outer.inner)的属性,即字符串、数字、布尔值。

In this example, we're creating a new constant (const newOuter...), using Object.assign, which creates an empty object ({}), copies outerobject ({ inner: 'initial value' }) into it and then copies a different object { inner: 'updated value' }overit.

在这个例子中,我们创建一个新的常数(const newOuter...),使用Object.assign,它创建一个空的对象({}),拷贝outer对象({ inner: 'initial value' })到它,然后复制一个不同的对象{ inner: 'updated value' }吧。

This way, in the end the newly created newOuterconstant will hold a value of { inner: 'updated value' }since the innerproperty got overridden. This newOuteris a brand new object, which is not linked to the object in state, so it can be mutated as needed and the state will stay the same and not changed until the command to update it is ran.

这样,最终新创建的newOuter常量将保留一个值,{ inner: 'updated value' }因为该inner属性被覆盖了。这newOuter是一个全新的对象,它没有链接到状态中的对象,因此可以根据需要对其进行变异,并且状态将保持不变,并且在运行更新它的命令之前不会更改。

The last part is to use setOuter()setter to replace the original outerin the state with a newly created newOuterobject (only the value will change, the property name outerwill not).

最后一部分是使用setOuter()setterouter用一个新创建的newOuter对象替换原来在state中的对象(只有值会改变,属性名outer不会)。

Now imagine we have a more deep state like state = { outer: { inner: { innerMost: 'initial value' } } }. We could try to create the newOuterobject and populate it with the outercontents from the state, but Object.assignwill not be able to copy innerMost's value to this newly created newOuterobject since innerMostis nested too deeply.

现在想象我们有一个更深的状态,比如state = { outer: { inner: { innerMost: 'initial value' } } }。我们可以尝试创建newOuter对象并用outer状态中的内容填充它,但由于嵌套太深,Object.assign无法将innerMost的值复制到这个新创建的newOuter对象innerMost

You could still copy inner, like in the example above, but since it's now an object and nota primitive, the referencefrom newOuter.innerwill be copied to the outer.innerinstead, which means that we will end up with local newOuterobject directly tied to the object in the state.

你仍然可以 copy inner,就像上面的例子一样,但是因为它现在是一个对象而不是一个基元,引用fromnewOuter.inner将被复制到outer.inner代替,这意味着我们最终将本地newOuter对象直接绑定到状态中的对象.

That means that in this case mutations of the locally created newOuter.innerwill directly affect the outer.innerobject (in state), since they are in fact became the same thing (in computer's memory).

这意味着在这种情况下,本地创建的突变newOuter.inner将直接影响outer.inner对象(在状态中),因为它们实际上变成了相同的东西(在计算机的内存中)。

Object.assigntherefore will only work if you have a relatively simple one level deep state structure with innermost members holding values of the primitive type.

Object.assign因此,只有当你有一个相对简单的一级深度状态结构,最里面的成员保存原始类型的值时才会起作用。

If you have deeper objects (2nd level or more), which you should update, don't use Object.assign. You risk mutating state directly.

如果您有更深的对象(第 2 级或更多),您应该更新,请不要使用Object.assign. 你冒着直接改变状态的风险。

2. Lodash's cloneDeep

2. Lodash 的 cloneDeep

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })

  React.useEffect(() => {
    console.log('Before the deep cloning:', outer.inner) // initial value
    const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
    newOuter.inner = 'updated value'
    console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<main id="react"></main>

Lodash's cloneDeepis way more simple to use. It performs a deep cloning, so it is a robust option, if you have a fairly complex state with multi-level objects or arrays inside. Just cloneDeep()the top-level state property, mutate the cloned part in whatever way you please, and setOuter()it back to the state.

Lodash 的cloneDeep使用起来更简单。它执行深度克隆,因此它是一个强大的选项,如果您有一个相当复杂的状态,其中包含多级对象或数组。只是cloneDeep()顶级状态属性,以任何你喜欢的方式改变克隆的部分,然后setOuter()它回到状态。

3. immutability-helper

3.不变性助手

const App = () => {
  const [outer, setOuter] = React.useState({ inner: 'initial value' })
  
  React.useEffect(() => {
    const update = immutabilityHelper
    console.log('Before the deep cloning and updating:', outer.inner) // initial value
    const newOuter = update(outer, { inner: { $set: 'updated value' } })
    console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
    setOuter(newOuter)
  }, [])

  console.log('In render:', outer.inner)

  return (
    <section>Inner property: <i>{outer.inner}</i></section>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/[email protected]"></script>

<main id="react"></main>

immutability-helpertakes it to a whole new level, and the cool thing about it is that it can not only $setvalues to state items, but also $push, $splice, $merge(etc.) them. Here is a list of commandsavailable.

immutability-helper把它带到一个全新的水平,它很酷的一点是它不仅可以用$set值来说明项目,还可以对它们进行$push, $splice, $merge(等)。这是可用命令列表

Side notes

旁注

Again, keep in mind, that setOuteronly modifies the first-level propertiesof the state object (outerin these examples), not the deeply nested (outer.inner). If it behaved in a different way, this question wouldn't exist.

再次记住,setOuter这只修改状态对象的第一级属性outer在这些示例中),而不是深度嵌套的 ( outer.inner)。如果它以不同的方式表现,这个问题就不会存在。

Which one is rightfor your project?

哪一个是正确的为您的项目?

If you don't want or can't use external dependencies, and have a simple state structure, stick to Object.assign.

如果您不想或不能使用外部依赖项,并且有一个简单的状态结构,请坚持使用Object.assign.

If you manipulate a huge and/or complex state, Lodash's cloneDeepis a wise choice.

如果你操纵一个巨大和/或复杂的 state,LodashcloneDeep是一个明智的选择。

If you need advanced capabilities, i.e. if your state structure is complex and you need to perform all kinds of operations on it, try immutability-helper, it's a very advanced tool which can be used for state manipulation.

如果您需要高级功能,即如果您的状态结构很复杂并且您需要对其执行各种操作,请尝试immutability-helper,这是一个非常高级的工具,可用于状态操作。

...or, do you reallyneed to do this at all?

……或者,你真的需要这样做吗?

If you hold a complex data in React's state, maybe this is a good time to think about other ways of handling it. Setting a complex state objects right in React components is not a straightforward operation, and I strongly suggest to think about different approaches.

如果你在 React 的状态下持有一个复杂的数据,也许这是考虑其他处理它的方式的好时机。在 React 组件中设置一个复杂的状态对象并不是一个简单的操作,我强烈建议考虑不同的方法。

Most likely you better be off keeping your complex data in a Redux store, setting it there using reducers and/or sagas and access it using selectors.

很可能您最好不要将复杂数据保存在 Redux 存储中,使用 reducer 和/或 sagas 将其设置在那里,并使用选择器访问它。

回答by Jonas

I had the same problem. Here's a simple solution that works !

我有同样的问题。这是一个有效的简单解决方案!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

回答by eicksl

According to the React documentation on setState, using Object.assignas suggested by other answers here is not ideal. Due to the nature of setState's asynchronous behavior, subsequent calls using this technique may override previous calls causing undesirable outcomes.

根据关于setState的 React 文档,Object.assign按照此处其他答案的建议使用并不理想。由于setState的异步行为的性质,使用此技术的后续调用可能会覆盖先前的调用,从而导致不良结果。

Instead, the React docs recommend to use the updater form of setStatewhich operates on the previous state. Keep in mind that when updating an array or object you must return a new array or objectas React requires us to preserve state immutability. Using ES6 syntax's spread operator to shallow copy an array, creating or updating a property of an object at a given index of the array would look like this:

相反,React 文档建议使用更新器形式,setState它在先前的状态上运行。请记住,在更新数组或对象时,您必须返回一个新的数组或对象,因为 React 要求我们保持状态不变性。使用 ES6 语法的扩展运算符浅拷贝数组,在数组的给定索引处创建或更新对象的属性将如下所示:

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

回答by Henrik Andersson

First get the item you want, change what you want on that object and set it back on the state. The way you're using state by only passing an object in getInitialStatewould be way easier if you'd use a keyed object.

首先获取您想要的项目,在该对象上更改您想要的内容并将其重新设置为状态。getInitialState如果您使用键控对象,则通过仅传入对象来使用状态的方式会更容易。

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}

回答by Jean Luo

Don't mutate the state in place. It can cause unexpected results. I have learned my lesson! Always work with a copy/clone, Object.assign()is a good one:

不要改变状态。它可能会导致意想不到的结果。我已经吸取了教训!始终使用副本/克隆,这Object.assign()是一个很好的选择:

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

回答by John

It's really simple.

这真的很简单。

First pull the entire items object from state, updated the part of the items object as desired, and put the entire items object back in state via setState.

首先从 state 中拉取整个 items 对象,根据需要更新 items 对象的一部分,然后通过 setState 将整个 items 对象放回 state。

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}

回答by Santiago M. Quintero

As none of the above options was ideal to me I ended up using map:

由于上述选项对我来说都不理想,因此我最终使用了地图:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })