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

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

但是像这样更新状态,

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

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


当前回答

虽然你问的是基于类的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示例,其中的状态被深度递归嵌套在树状数据结构中。

其他回答

这是第一个答案的变体,不需要任何额外的包、库或特殊函数。

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

为了设置特定嵌套字段的状态,您已经设置了整个对象。为此,我创建了一个变量newState,并首先使用ES2015扩展操作符将当前状态的内容扩散到该变量中。然后,我用新值替换了this.state.flag的值(因为我在将当前状态扩展到对象后设置了flag: value,所以当前状态下的flag字段将被覆盖)。然后,我简单地将someProperty的状态设置为newState对象。

我发现这对我很有用,在我的案例中有一个项目表单,例如,你有一个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>
)

让我知道!

虽然你问的是基于类的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示例,其中的状态被深度递归嵌套在树状数据结构中。

为了让事情变得通用,我研究了@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);
}

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

还有两个选项没有提到:

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