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

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

但是像这样更新状态,

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

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


当前回答

我发现这对我很有用,在我的案例中有一个项目表单,例如,你有一个id和一个名称,我宁愿为一个嵌套的项目维护状态。

return (
  <div>
      <h2>Project Details</h2>
      <form>
        <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
        <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
      </form> 
  </div>
)

让我知道!

其他回答

我们使用Immer https://github.com/mweststrate/immer来处理这类问题。

只是在我们的一个组件中替换了这段代码

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

用这个

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));

使用immer,您可以将您的状态作为“普通对象”处理。 神奇的事情发生在代理对象的幕后。

免责声明

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没有被重新渲染,因为道具未能传播。

如果你在你的项目中使用formik,它有一些简单的方法来处理这些东西。下面是使用formik最简单的方法。

首先在formik initivalues属性或react中设置初始值。状态

这里,初始值是在react状态下定义的

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

在formik initiValues属性中定义以上的initialValues字段

<Formik
 initialValues={this.state.data}
 onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
  <Form>
    <Field type="checkbox" name="fy.active" onChange={(e) => {
      const value = e.target.checked;
      if(value) setFieldValue('fy.active', 'Y')
      else setFieldValue('fy.active', 'N')
    }}/>
  </Form>
)}
</Formik>

制作一个控制台来检查状态更新为字符串而不是布尔值formik setFieldValue函数来设置状态或使用react调试器工具来查看formik状态值的变化。

虽然你问的是基于类的React组件的状态,但useState钩子也存在同样的问题。更糟糕的是:useState钩子不接受部分更新。所以当useState钩子被引入时,这个问题就变得非常相关了。

我决定发布以下答案,以确保这个问题涵盖了使用useState钩子的更现代的场景:

如果你有:

const [state, setState] = useState({
  someProperty: {
    flag: true,
    otherNestedProp: 1
  },
  otherProp: 2
})

你可以通过克隆当前数据并修补所需的数据段来设置嵌套属性,例如:

setState(current => { ...current,
  someProperty: { ...current.someProperty,
    flag: false
  }
});

或者您可以使用Immer库来简化对象的克隆和修补。

或者你可以使用Hookstate库(免责声明:我是一名作家)来简单地管理复杂的(本地和全局)状态数据,并提高性能(阅读:不用担心渲染优化):

import { useHookstate } from '@hookstate/core'

const state = useHookstate({
  someProperty: {
    flag: true,
    otherNestedProp: 1
  },
  otherProp: 2
})

获取要渲染的字段:

state.someProperty.flag.get()
// or 
state.get().someProperty.flag

设置嵌套字段:

state.someProperty.flag.set(false)

下面是Hookstate示例,其中的状态被深度递归嵌套在树状数据结构中。

我用了这个方法。

如果你有一个这样的嵌套状态:

   this.state = {
          formInputs:{
            friendName:{
              value:'',
              isValid:false,
              errorMsg:''
            },
            friendEmail:{
              value:'',
              isValid:false,
              errorMsg:''
            }
}

你可以声明handleChange函数来复制当前状态并重新赋值

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

这里是带有事件侦听器的HTML

<input type="text" onChange={this.handleChange} " name="friendName" />