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

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

但是像这样更新状态,

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

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


当前回答

我用reduce搜索做嵌套更新:

例子:

状态中的嵌套变量:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

功能:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

Use:

<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>

其他回答

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

为了为嵌套对象设置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更新状态,请参阅此回答。

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

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

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

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

下面是一个完整的例子,使用嵌套状态(一层)和这个答案中的解决方案,对于一个实现为类的组件:

class CaveEditModal extends React.Component {

  // ...

  constructor(props, context) {
    super(props);
    this.state = {

      tabValue: '1',
      isModalOpen: this.props.isModalOpen,

      // ...
      caveData: {
        latitude: 1,
        longitude: 2      
      }
    };

    // ... 

    const updateNestedFieldEvent = fullKey => ev => { 
      
      var [parentProperty, _key] = fullKey.split(".", 2);

      this.setState({[parentProperty]: { ...this.state[parentProperty], [_key]: ev.target.value} });
    };
    // ...

    this.handleLatitudeChange = updateNestedFieldEvent('caveData.latitude');
    this.handleLongitudeChange = updateNestedFieldEvent('caveData.longitude');
  }

  render () {    
    return (
      <div>
         <TextField id="latitude" label="Latitude" type="number" value={this.state.caveData.latitude} onChange={this.handleLatitudeChange} />
         <TextField id="longitude" label="Longitude" type="number" value={this.state.caveData.longitude} onChange={this.handleLongitudeChange} />
         <span>lat={this.state.caveData.latitude} long={this.state.caveData.longitude}</span>
      </div>
    );
  };

}

注意,状态更新器函数updateNestedFieldEvent只适用于一层嵌套对象,比如a.b,而不是a.b.c。

setInputState((pre)=> ({...pre,[parentKey]: {...pre[parentKey], [childKey]: value}}));

我喜欢这个