如何更新 React 中的嵌套状态属性

IT技术 javascript reactjs ecmascript-6 setstate
2021-01-08 23:50:27

我正在尝试使用这样的嵌套属性来组织我的状态:

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

但是像这样更新状态,

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

不起作用。如何正确地做到这一点?

6个回答

为了setState嵌套对象,您可以遵循以下方法,因为我认为 setState 不处理嵌套更新。

var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})

这个想法是创建一个虚拟对象对其执行操作,然后用更新的对象替换组件的状态

现在,扩展运算符仅创建对象的一层嵌套副本。如果您的状态是高度嵌套的,例如:

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

您可以在每个级别使用扩展运算符 setState ,例如

this.setState(prevState => ({
    ...prevState,
    someProperty: {
        ...prevState.someProperty,
        someOtherProperty: {
            ...prevState.someProperty.someOtherProperty, 
            anotherProperty: {
               ...prevState.someProperty.someOtherProperty.anotherProperty,
               flag: false
            }
        }
    }
}))

然而,随着状态变得越来越嵌套,上述语法变得越来越难看,因此我建议您使用immutability-helper包来更新状态。

请参阅有关如何使用 更新状态的答案immutability-helper

这对我不起作用..我正在使用反应版本@15.6.1
2021-03-09 23:50:27
@Stophface 我们可以使用 Lodash 进行深度克隆,但并不是每个人都会将这个库包含在 setState 中
2021-03-24 23:50:27
这不违反reactjs.org/docs/...吗?如果对嵌套对象的两个更改被批处理,最后一个更改将覆盖第一个更改。所以最正确的方法是:this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}}) 虽然有点乏味......
2021-03-26 23:50:27
我认为你不需要...prevState,最后一个例子中的,你只需要someProperty它的孩子
2021-03-30 23:50:27
@Ohgodwhy ,不,这不是直接访问状态,因为 {...this.state.someProperty} 将新对象重新调整为someProperty,我们正在修改它
2021-04-06 23:50:27

写在一行

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
为了完整性,我已经添加如何使用 useState 钩子实现相同的答案
2021-03-22 23:50:27
@RaghuTeja 也许只是制作 this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });可以避免这个问题?
2021-03-26 23:50:27
我相信那是说,不要this.state立即阅读setState并期望它具有新的value。this.statesetState. 或者后者是否也存在我不清楚的问题?
2021-04-04 23:50:27
作为trpt4him说,链接你给了有关访问的问题举行会谈this.state之后setState此外,我们不会传递this.statesetState运营商扩展性。它与 Object.assign() 相同。github.com/tc39/proposal-object-rest-spread
2021-04-04 23:50:27
建议对 setState 使用更新程序,而不是在 setState 中直接访问 this.state。reactjs.org/docs/react-component.html#setstate
2021-04-05 23:50:27

有时直接的答案不是最好的:)

精简版:

这段代码

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

应该简化为类似

this.state = {
    somePropertyFlag: true
}

长版:

目前你不应该想在 React 中使用嵌套状态因为 React 不是面向嵌套状态的,所以这里提出的所有解决方案看起来都是 hacks。他们不使用框架,而是与之抗争。他们建议编写不太清晰的代码,以用于对某些属性进行分组的可疑目的。因此,它们作为挑战的答案非常有趣,但实际上毫无用处。

让我们想象以下状态:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

如果只更改 的值会发生什么child1React 不会重新渲染视图,因为它使用浅比较并且它会发现parent属性没有改变。顺便说一句,直接改变状态对象通常被认为是一种不好的做法。

所以你需要重新创建整个parent对象。但在这种情况下,我们会遇到另一个问题。React 会认为所有的孩子都改变了他们的value观,并会重新渲染所有的value观。当然,这对性能不利。

仍然可以通过编写一些复杂的逻辑来解决该问题,shouldComponentUpdate()但我更愿意在这里停止并使用简短版本的简单解决方案。

我想为你建造一座祭坛,每天早上向它祈祷。我花了三天时间才得出这个完美解释 OBVIOUS 设计决策的答案。每个人都只是尝试使用扩展运算符或进行其他黑客攻击,只是因为嵌套状态看起来更易于人类阅读。
2021-03-09 23:50:27
我认为有些用例嵌套状态非常好。例如,动态跟踪键值对的状态。在此处查看如何更新状态中单个条目的状态:reactjs.org/docs/...
2021-03-10 23:50:27
这,一百万次。我花了很多时间来处理嵌套参数,因为网上无益的评论表明它可能以某种令人困惑的方式出现。react 文档应该在 setState 部分的开头指出这一点!
2021-03-15 23:50:27
似乎 React 不是用来表示任何存储为非平凡深度的递归结构的。这有点令人失望,因为几乎所有复杂的应用程序(gmail、文件管理器、任何文档管理系统……)中都出现了树视图。我已经将 React 用于这些事情,但总是有一个 React 狂热者告诉每个人你正在做反模式并建议解决方案是在每个节点的状态下保留树的深层副本(是的,80 个副本)。我很想看到一个不违反任何 React 规则的非 hacky 树视图组件。
2021-03-25 23:50:27
那么,您如何建议使用 React 来做诸如渲染交互式树之类的事情(例如,我的公司在世界不同地区有一堆传感器,每种传感器类型不同,具有不同的属性,位于不同的位置(80+ ),当然,它们按层次结构组织,因为每次部署都要花费数千美元并且是一个完整的项目,因此没有层次结构就不可能管理它们)。我很好奇你如何不在 React 中将树之类的东西建模为......树。
2021-03-31 23:50:27

免责声明

React 中的嵌套状态是错误的设计

阅读这个优秀的答案

 

这个答案背后的推理:

React 的 setState 只是一种内置的便利,但您很快就会意识到它有其局限性。使用自定义属性和智能使用 forceUpdate为您提供更多。例如:

class MyClass extends React.Component {
    myState = someObject
    inputValue = 42
...

例如,MobX 完全抛弃状态并使用自定义的可观察属性。
在 React 组件中使用 Observables 而不是 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请参阅链接示例沙箱

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

警告

即使包含状态的组件正确更新和重新渲染除了这个 gotcha,props也将无法传播给孩子(请参阅下面的 Spymaster 评论)仅当您知道自己在做什么时才使用此技术。

例如,您可以传递一个更改过的平面props,该props可以轻松更新和传递。

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

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

this.props.complexNestedProp === nextProps.complexNestedProp

每当父组件更新时,组件都会重新渲染,调用后this.setStatethis.forceUpdate父组件中就是这种情况

改变状态沙箱的影响

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

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

状态流 状态流嵌套

无论如何,在这里您可以看到Nested PureChildClass由于props无法传播而没有重新渲染。

@MegaProger 这是逗号运算符 ~MDN事实上,(exp1, {})setState调用中只返回一个空对象也可以,但我不想不必要地创建新对象。沙盒
2021-03-09 23:50:27
@tobiv 最好将获取时的数据转换为不同的结构,但这取决于用例。如果您将嵌套对象或对象数组视为某些数据的紧凑单元,则可以将它们置于某种状态当您需要存储 UI 开关或其他可观察数据(即应立即更改视图的数据)时,就会出现问题,因为它们必须是平坦的,以便正确触发内部 React 差异算法。对于其他嵌套对象的更改,很容易触发this.forceUpdate()或实现特定的shouldComponentUpdate.
2021-03-21 23:50:27
你应该删除你的陈述“最好的答案”。你怎么知道这是最好的答案?你的陈述具有误导性和过度自以为是。
2021-03-24 23:50:27
有点离题 - 你能提供一些信息为什么 (expression1, expression2) 以this.setState(state => (state.nested.flag = false, state))这种方式工作吗?(文档、mdn、搜索请求等)
2021-03-25 23:50:27
你不应该在任何地方改变任何反应状态。如果嵌套组件使用 state.nested 或 state.another 中的数据,则它们不会重新渲染,其子组件也不会。这是因为对象没有改变,因此 React 认为什么都没有改变。
2021-03-26 23:50:27

如果您使用的是 ES2015,您可以访问 Object.assign。您可以按如下方式使用它来更新嵌套对象。

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

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

编辑:向分配函数添加了一个空对象作为目标,以确保状态不会像 carkod 指出的那样直接改变。

我可能是错的,但我不认为你在没有ES2015 的情况下使用 React
2021-03-25 23:50:27