我试图通过使用这样的嵌套属性来组织我的状态:

this.state = {
   someProperty: {
      flag:true
   }
}

但是像这样更新状态,

this.setState({ someProperty.flag: false });

是行不通的。怎样才能正确地做到这一点呢?


当前回答

为了让事情变得通用,我研究了@ShubhamKhatri和@Qwerty的答案。

状态对象

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

输入控件

<input
  value={this.state.name}
  onChange={this.updateState}
  type="text"
  name="name"
/>
<input
  value={this.state.grandParent.parent1.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent1.child"
/>
<input
  value={this.state.grandParent.parent2.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent2.child"
/>

updateState方法

setState作为@ShubhamKhatri的答案

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState作为@Qwerty的答案

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

注意:上述方法不适用于数组

其他回答

你应该把new state传递给setState。 新状态的引用必须与旧状态的引用不同。

所以试试这个:

this.setState({
    ...this.state,
    someProperty: {...this.state.someProperty, flag: true},
})

根据框架的标准,不确定这在技术上是否正确,但有时您只是需要更新嵌套对象。这是我使用钩子的解决方案。

setInputState ({ ... inputState, [括号]:内务部[括号],[childKey]: });

还有两个选项没有提到:

如果您有深度嵌套的状态,请考虑是否可以重新构造子对象,使其位于根节点。这使得数据更容易更新。 Redux文档中列出了许多方便的库来处理不可变状态。我推荐Immer,因为它允许您以可变的方式编写代码,但在幕后处理必要的克隆。它还会冻结生成的对象,这样以后就不会意外地改变它。

如果你正在使用ES2015,你可以访问Object.assign。您可以使用它来更新嵌套对象。

this.setState({
  someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});

您将更新的属性与现有属性合并,并使用返回的对象更新状态。

编辑:将一个空对象作为目标添加到赋值函数中,以确保状态不会像carkod指出的那样直接发生突变。

免责声明

React中的嵌套状态是错误的设计 阅读这篇精彩的回答。

 

这个答案背后的原因是: React的setState只是一个内置的方便,但你很快就会意识到 它有它的局限性。使用自定义属性和智能使用 forceUpdate提供更多信息。 例如: MyClass扩展React。组件{ myState = someObject inputValue = 42 … 例如,MobX完全抛弃状态,使用自定义可观察属性。 在React组件中使用observable而不是state。

 


你痛苦的答案——请看这里的例子

还有另一种更短的方法来更新任何嵌套的属性。

this.setState(state => {
  state.nested.flag = false
  state.another.deep.prop = true
  return state
})

在一行上

 this.setState(state => (state.nested.flag = false, state))

注意:这里是逗号操作符~MDN,在这里(沙盒)看到它的作用。

它类似于(尽管这不会改变状态引用)

this.state.nested.flag = false
this.forceUpdate()

关于这个上下文中forceUpdate和setState之间的细微差别,请参阅链接示例和沙盒。

当然,这是在滥用一些核心原则,因为状态应该是只读的,但是由于您立即丢弃了旧状态并用新状态替换它,所以这是完全可以的。

警告

即使包含状态的组件将正确更新和重新渲染(除了这个gotcha),道具将无法传播给孩子(见Spymaster的评论下面)。只有当你知道你在做什么时才使用这个技巧。

例如,您可以传递一个已更改的平面道具,该道具已更新并易于传递。

render(
  //some complex render with your nested state
  <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
)

现在即使complexNestedProp的引用没有改变(shouldComponentUpdate)

this.props.complexNestedProp === nextProps.complexNestedProp

每当父组件更新时,组件都会重新呈现,这是在调用this之后的情况。setState或this。在父类中执行forceUpdate。

改变状态沙箱的效果

使用嵌套状态和直接改变状态是危险的,因为不同的对象可能(有意或无意地)持有对状态的不同(旧的)引用,并且可能不一定知道何时更新(例如使用PureComponent时,或者如果shouldComponentUpdate被实现以返回false)或意图显示旧数据,如下例所示。

想象一下,一个应该呈现历史数据的时间轴,改变手上的数据将导致意想不到的行为,因为它也会改变之前的项目。

无论如何,在这里你可以看到嵌套的PureChildClass没有被重新渲染,因为道具未能传播。