我正在创建一个应用程序,用户可以设计自己的形式。例如,指定字段的名称和应该包括的其他列的详细信息。

该组件可作为JSFiddle使用。

初始状态是这样的

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>
    );
  }

当用户改变任何值时,我想更新状态,但我很难找到正确的对象:

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>
    );
  }
});

我该怎么做呢?setState让它更新项目[1].name ?


当前回答

使用数组映射与箭头函数,在一行

this.setState({
    items: this.state.items.map((item, index) =>
      index === 1 ? { ...item, name: 'newName' } : item,
   )
})

其他回答

尝试这肯定会工作,其他情况下我尝试过,但没有工作

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});

不要原地改变状态。它会导致意想不到的结果。我已经吸取教训了!总是使用复制/克隆,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

要在React的状态下修改嵌套很深的对象/变量,通常使用三种方法:指定,immutability-helper和cloneDeep从Lodash。

还有许多其他不太流行的第三方库可以实现这一点,但在这个回答中,我将只介绍这三个选项。此外,还存在一些附加的JavaScript方法,如数组扩展(参见@mpen的回答),但它们不是非常直观、易于使用,并且能够处理所有状态操作情况。

正如在投票最多的回答评论中无数次指出的那样,其作者建议直接改变状态:不要这样做。这是一个普遍存在的React反模式,它将不可避免地导致不必要的后果。学习正确的方法。

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

给定这个状态对象结构:

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

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

1. 香草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>

记住,那个对象。Assign不会执行深度复制,因为它只复制属性值,这就是为什么它所做的被称为浅复制(参见注释)。

要做到这一点,我们应该只操作基本类型(outer.inner)的属性,即字符串、数字、布尔值。

在本例中,我们使用Object创建了一个新常量(const newOuter…)。赋值,它创建一个空对象({}),将外部对象({inner: '初始值'})复制到该对象中,然后在其上复制一个不同的对象{inner: '更新值'}。

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

最后一部分是使用setOuter() setter将状态中的原始outer替换为新创建的newOuter对象(只有值会改变,属性名称outer不会改变)。

现在想象我们有一个更深层的状态,比如state = {outer: {inner: {innerMost: 'initial value'}}}。我们可以尝试创建newOuter对象,并用来自state, but object的外部内容填充它。assign函数不能将innerMost的值复制到这个新创建的newOuter对象,因为innerMost嵌套得太深了。

您仍然可以复制inner,就像上面的例子一样,但由于它现在是一个对象而不是一个原语,因此newOuter的引用。内部将被复制到外部。而不是inner,这意味着我们将以直接绑定到状态中的对象的局部newOuter对象结束。

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

对象。因此,只有当您有一个相对简单的一级深度状态结构,其中最里面的成员持有原始类型的值时,Assign才有效。

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

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的cloneDeep使用起来更简单。它执行深度克隆,因此如果您有一个相当复杂的状态,其中包含多级对象或数组,那么它是一个健壮的选项。只需要cloneDeep()顶级的状态属性,以任何您喜欢的方式改变克隆的部分,然后setOuter()将其恢复到状态。

3.immutability-helper

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/immutability-helper@3.0.0"></script> <main id="react"></main>

Immutability-helper将它带到了一个全新的水平,它的最酷之处在于它不仅可以$set值来状态项,还可以$push, $splice, $merge(等等)它们。下面是可用的命令列表。

一边笔记

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

哪个适合你的项目?

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

如果你要操纵一个庞大或复杂的状态,Lodash的cloneDeep是一个明智的选择。

如果你需要高级功能,比如你的状态结构很复杂,你需要对它执行各种操作,试试immutability-helper,它是一种非常高级的工具,可以用于状态操作。

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

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

大多数情况下,您最好将复杂的数据保存在Redux存储中,使用reducer和/或sagas将其设置在那里,并使用选择器访问它。

错误的方式!

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

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

正如许多优秀的开发者在评论中指出的那样:改变状态是错误的!

我花了好长时间才想明白。上面的工作,但它带走了React的力量。例如,componentDidUpdate不会将其视为更新,因为它是直接修改的。

所以正确的方法应该是:

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

有时候在React中,突变克隆的数组会影响到原来的数组,这种方法永远不会导致突变:

    const myNewArray = Object.assign([...myArray], {
        [index]: myNewItem
    });
    setState({ myArray: myNewArray });

或者如果你只是想更新一个项的属性:

    const myNewArray = Object.assign([...myArray], {
        [index]: {
            ...myArray[index],
            prop: myNewValue
        }
    });
    setState({ myArray: myNewArray });