【问题标题】:How to update nested state properties in React如何在 React 中更新嵌套状态属性
【发布时间】:2017-08-19 19:48:05
【问题描述】:

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

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

但是像这样更新状态,

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

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

【问题讨论】:

  • 你是什么意思不起作用?这不是一个很好的问题——发生了什么?有什么错误吗?什么错误?
  • 答案是不要在 React 中使用嵌套状态。阅读this优秀答案。
  • 应该改用什么?
  • 简单地不使用嵌套状态对于当今 React 的广泛使用是一个不可接受的答案。这种情况将会出现,开发人员需要一个答案。

标签: javascript reactjs ecmascript-6 setstate


【解决方案1】:

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

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

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

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

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

您可以在每个级别使用扩展运算符设置状态,例如

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

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

请参阅this answer,了解如何使用immutability-helper 更新状态。

【讨论】:

  • @Ohgodwhy ,不,这不是直接访问状态,因为 {...this.state.someProperty} 将新对象返回给someProperty,我们正在修改它
  • 这对我不起作用..我正在使用反应版本@15.6.1
  • @Stophface 我们确实可以使用 Lodash 进行深度克隆,但并不是每个人都会将这个库包含在 setState 中
  • 这不违反reactjs.org/docs/… 吗?如果嵌套对象的两个更改被批处理,最后一个更改将覆盖第一个更改。所以最正确的方法是:this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}}) 虽然有点乏味...
  • 我认为你不需要最后一个示例中的 ...prevState,,你只需要 someProperty 及其子项
【解决方案2】:

写成一行

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

【讨论】:

  • 建议使用setState的updater,而不是直接访问setState中的this.state。 reactjs.org/docs/react-component.html#setstate
  • 我相信这就是说,不要在setState 之后立即阅读this.state 并期望它具有新的价值。与在setState 内调用this.state 相比。还是后者还有一个我不清楚的问题?
  • 正如trpt4him所说,你给出的链接是在setState之后访问this.state的问题。此外,我们不会将this.state 传递给setState 运算符扩展属性。它与 Object.assign() 相同。 github.com/tc39/proposal-object-rest-spread
  • @RaghuTeja 也许只是让this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} }); 可以避免这个问题?
  • 我已经添加了 the answer 如何使用 useState 钩子实现相同的完整性。
【解决方案3】:

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

短版:

这段代码

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

应该简化为类似

this.state = {
    somePropertyFlag: true
}

加长版:

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

让我们想象以下状态:

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

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

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

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

【讨论】:

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

免责声明

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

阅读this excellent answer

 

这个答案背后的原因:

React 的 setState 只是一个内置的便利,但你很快就会意识到 它有它的极限。使用自定义属性和智能使用 forceUpdate 给你更多。 例如:

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

例如,MobX 完全抛弃状态并使用自定义的可观察属性。
Use Observables instead of state in React components.

 


你的痛苦的答案 - see example here

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

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

单行

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

注意:这里是Comma operator ~MDN,查看实际操作here (Sandbox)

类似于(虽然这不会改变状态引用)

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

有关forceUpdatesetState 在此上下文中的细微差别,请参阅链接的examplesandbox

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

警告

即使包含状态的组件正确更新和重新渲染(except this 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.setStatethis.forceUpdate 之后的情况。

改变状态sandbox的效果

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

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

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

【讨论】:

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

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

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

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

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

【讨论】:

  • 我可能是错的,但我不认为你在使用 React 没有 ES2015。
【解决方案6】:
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);

【讨论】:

  • 在这里似乎是更优雅的解决方案,1) 避免在 setState 中进行繁琐的传播;2) 将新状态很好地放入 setState 中,避免直接在 setState 中发生变异。最后但并非最不重要的 3) 避免使用任何库来完成简单的任务
  • 这两个例子是不等价的。如果property 包含多个嵌套属性,第一个将删除它们,第二个不会。 codesandbox.io/s/nameless-pine-42thu
  • Qwerty,您是对的,感谢您的努力。我删除了 ES6 版本。
  • 直截了当!
  • 我使用了您的解决方案。我的状态 obj 很小,而且运行良好。 React 是只渲染改变的状态还是渲染所有的东西?
【解决方案7】:

我们使用 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,您可以将状态作为“普通对象”处理。 魔术发生在代理对象的幕后。

【讨论】:

    【解决方案8】:

    有很多库可以帮助解决这个问题。例如,使用immutability-helper:

    import update from 'immutability-helper';
    
    const newState = update(this.state, {
      someProperty: {flag: {$set: false}},
    };
    this.setState(newState);
    

    使用lodash/fp 设置:

    import {set} from 'lodash/fp';
    
    const newState = set(["someProperty", "flag"], false, this.state);
    

    使用lodash/fp合并:

    import {merge} from 'lodash/fp';
    
    const newState = merge(this.state, {
      someProperty: {flag: false},
    });
    

    【讨论】:

    • 如果您使用lodash,您可能想尝试_.cloneDeep并将状态设置为克隆副本。
    • @YixingLiu:我已经更新了答案以使用lodash/fp,所以我们不必担心突变。
    • 除了语法之外,在 lodash/fp 中选择 mergeset 函数有什么好处吗?
    • 好答案,也许您想补充一点,您还必须为 lodash/fp 方法调用 this.setState(newState)。另一个问题是 lodash .set (non-fp) 的参数顺序不同,这让我有一段时间感到困惑。
    • Merge 有一些奇怪的行为,当调用this.setState(newState) 时,它会在this.state 中创建另一个名为newState 的状态项。知道为什么吗?
    【解决方案9】:

    这是此线程中给出的第一个答案的变体,不需要任何额外的包、库或特殊功能。

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

    为了设置特定嵌套字段的状态,您已经设置了整个对象。我通过创建一个变量 newState 并使用 ES2015 spread operator 将当前状态的内容first传播到其中来做到这一点。然后,我将this.state.flag的值替换为新值(由于我设置flag: value之后我将当前状态传播到对象中,当前状态下的flag字段被覆盖) .然后,我只需将someProperty 的状态设置为我的newState 对象。

    【讨论】:

      【解决方案10】:

      尽管您询问了基于类的 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 library(免责声明:我是作者)来完全简单地管理复杂(本地和全局)状态数据并提高性能(阅读:不用担心渲染优化):

      import { useStateLink } from '@hookstate/core' 
      const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })
      

      获取要渲染的字段:

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

      设置嵌套字段:

      state.nested.someProperty.nested.flag.set(false)
      

      这是 Hookstate 示例,其中状态深度/递归嵌套在 tree-like data structure 中。

      【讨论】:

        【解决方案11】:

        我使用了这个解决方案。

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

           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" />
        

        【讨论】:

        • 已确认。如果您已经在处理已经建立的具有嵌套属性的项目,这将非常有用。
        • Object.assign 只做一个浅拷贝,所以你正在改变一个嵌套对象。不要改变以前的状态。
        【解决方案12】:

        虽然嵌套并不是您真正应该如何对待组件状态的方式,但有时对于单层嵌套来说很容易。

        对于这样的状态

        state = {
         contact: {
          phone: '888-888-8888',
          email: 'test@test.com'
         }
         address: {
          street:''
         },
         occupation: {
         }
        }
        

        我使用的可重复使用的方法如下所示。

        handleChange = (obj) => e => {
          let x = this.state[obj];
          x[e.target.name] = e.target.value;
          this.setState({ [obj]: x });
        };
        

        然后只需为您要处理的每个嵌套传递 obj 名称...

        <TextField
         name="street"
         onChange={handleChange('address')}
         />
        

        【讨论】:

        • 这会改变当前状态对象而不是创建副本。不要改变状态!
        【解决方案13】:

        创建状态副本:

        let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))
        

        对此对象进行更改:

        someProperty.flag = "false"
        

        现在更新状态

        this.setState({someProperty})
        

        【讨论】:

        • setState 是异步的。因此,当它更新时,其他人可以调用此函数并复制过时版本的状态。
        【解决方案14】:

        我看到每个人都给出了基于类的组件状态更新解决方案,这是预期的,因为他要求这样做,但我试图为钩子提供相同的解决方案。

        const [state, setState] = useState({
            state1: false,
            state2: 'lorem ipsum'
        })
        

        现在,如果您只想更改嵌套对象键 state1,则可以执行以下任何操作:

        流程 1

        let oldState = state;
        oldState.state1 = true
        setState({...oldState);
        

        流程 2

        setState(prevState => ({
            ...prevState,
            state1: true
        }))
        

        我最喜欢过程2。

        【讨论】:

        • “进程 1”在 React 中是一种反模式,因为它会改变当前状态对象。
        【解决方案15】:

        还有两个尚未提及的选项:

        1. 如果您有深度嵌套的状态,请考虑是否可以重新构造子对象以使其位于根部。这使得数据更容易更新。
        2. 有许多方便的库可用于处理不可变状态listed in the Redux docs。我推荐 Immer,因为它允许您以可变方式编写代码,但可以在幕后处理必要的克隆。它还会冻结生成的对象,这样您以后就不会意外地对其进行变异。

        【讨论】:

          【解决方案16】:

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

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

          【讨论】:

            【解决方案17】:

            我非常重视 already voiced 创建组件状态的完整副本的担忧。话虽如此,我强烈建议Immer

            import produce from 'immer';
            
            <Input
              value={this.state.form.username}
              onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
            

            这应该适用于 React.PureComponent(即 React 的浅层状态比较),因为 Immer 巧妙地使用代理对象来有效地复制任意深度的状态树。与 Immutability Helper 等库相比,Immer 的类型安全性更高,非常适合 Javascript 和 Typescript 用户。


            Typescript实用函数

            function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: 
            Draft<Readonly<S>>) => any) {
              comp.setState(produce(comp.state, s => { fn(s); }))
            }
            
            onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
            

            【讨论】:

              【解决方案18】:

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

              setInputState({
                              ...inputState,
                              [parentKey]: { ...inputState[parentKey], [childKey]: value },
                          });

              【讨论】:

              • 你实现了吗?
              【解决方案19】:

              如果要动态设置状态


              以下示例动态设置表单的状态,其中状态中的每个键都是对象

               onChange(e:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
                  this.setState({ [e.target.name]: { ...this.state[e.target.name], value: e.target.value } });
                }
              

              【讨论】:

                【解决方案20】:

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

                告诉我!

                【讨论】:

                  【解决方案21】:
                  stateUpdate = () => {
                      let obj = this.state;
                      if(this.props.v12_data.values.email) {
                        obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
                      }
                      this.setState(obj)
                  }
                  

                  【讨论】:

                  • 我不认为这是正确的,因为 obj 只是对状态的引用,所以通过它你正在改变实际的 this.state 对象
                  【解决方案22】:

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

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

                  这里,初始值是在反应状态中定义的

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

                  为 formik initiValues 属性中的 formik 字段定义上面的 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>
                  

                  做一个控制台来检查状态更新到string而不是booleanformiksetFieldValue函数来设置状态或使用react调试器工具来查看formik状态值的变化。

                  【讨论】:

                    【解决方案23】:

                    这显然不是正确或最好的方法,但在我看来它更清晰:

                    this.state.hugeNestedObject = hugeNestedObject; 
                    this.state.anotherHugeNestedObject = anotherHugeNestedObject; 
                    
                    this.setState({})
                    

                    但是,React 本身应该迭代思想嵌套对象并相应地更新状态和 DOM,而这还不存在。

                    【讨论】:

                      【解决方案24】:

                      您可以通过对象传播来做到这一点 代码:

                       this.setState((state)=>({ someProperty:{...state.someProperty,flag:false}})
                      

                      这将适用于更多的嵌套属性

                      【讨论】:

                        【解决方案25】:

                        将此用于多输入控件和动态嵌套名称

                        <input type="text" name="title" placeholder="add title" onChange={this.handleInputChange} />
                        <input type="checkbox" name="chkusein" onChange={this.handleInputChange} />
                        <textarea name="body" id="" cols="30" rows="10" placeholder="add blog content" onChange={this.handleInputChange}></textarea>
                        

                        代码可读性很强

                        处理程序

                        handleInputChange = (event) => {
                                const target = event.target;
                                const value = target.type === 'checkbox' ? target.checked : target.value;
                                const name = target.name;
                                const newState = { ...this.state.someProperty, [name]: value }
                                this.setState({ someProperty: newState })
                            }
                        

                        【讨论】:

                          【解决方案26】:

                          您应该将新状态传递给 setState。 新状态的引用必须不同于旧状态。

                          所以试试这个:

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

                          【讨论】:

                          • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
                          【解决方案27】:

                          这样的事情可能就足够了,

                          const isObject = (thing) => {
                              if(thing && 
                                  typeof thing === 'object' &&
                                  typeof thing !== null
                                  && !(Array.isArray(thing))
                              ){
                                  return true;
                              }
                              return false;
                          }
                          
                          /*
                            Call with an array containing the path to the property you want to access
                            And the current component/redux state.
                          
                            For example if we want to update `hello` within the following obj
                            const obj = {
                               somePrimitive:false,
                               someNestedObj:{
                                  hello:1
                               }
                            }
                          
                            we would do :
                            //clone the object
                            const cloned = clone(['someNestedObj','hello'],obj)
                            //Set the new value
                            cloned.someNestedObj.hello = 5;
                          
                          */
                          const clone = (arr, state) => {
                              let clonedObj = {...state}
                              const originalObj = clonedObj;
                              arr.forEach(property => {
                                  if(!(property in clonedObj)){
                                      throw new Error('State missing property')
                                  }
                          
                                  if(isObject(clonedObj[property])){
                                      clonedObj[property] = {...originalObj[property]};
                                      clonedObj = clonedObj[property];
                                  }
                              })
                              return originalObj;
                          }
                          
                          const nestedObj = {
                              someProperty:true,
                              someNestedObj:{
                                  someOtherProperty:true
                              }
                          }
                          
                          const clonedObj = clone(['someProperty'], nestedObj);
                          console.log(clonedObj === nestedObj) //returns false
                          console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
                          console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true
                          
                          console.log()
                          const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
                          console.log(clonedObj2 === nestedObj) // returns false
                          console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
                          //returns true (doesn't attempt to clone because its primitive type)
                          console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 
                          

                          【讨论】:

                            【解决方案28】:

                            我知道这是一个老问题,但仍然想分享我是如何做到这一点的。假设构造函数中的状态如下所示:

                              constructor(props) {
                                super(props);
                            
                                this.state = {
                                  loading: false,
                                  user: {
                                    email: ""
                                  },
                                  organization: {
                                    name: ""
                                  }
                                };
                            
                                this.handleChange = this.handleChange.bind(this);
                              }
                            

                            我的handleChange函数是这样的:

                              handleChange(e) {
                                const names = e.target.name.split(".");
                                const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
                                this.setState((state) => {
                                  state[names[0]][names[1]] = value;
                                  return {[names[0]]: state[names[0]]};
                                });
                              }
                            

                            并确保相应地命名输入:

                            <input
                               type="text"
                               name="user.email"
                               onChange={this.handleChange}
                               value={this.state.user.firstName}
                               placeholder="Email Address"
                            />
                            
                            <input
                               type="text"
                               name="organization.name"
                               onChange={this.handleChange}
                               value={this.state.organization.name}
                               placeholder="Organization Name"
                            />
                            

                            【讨论】:

                            • state[names[0]][names[1]] = value 这会改变之前的状态对象,这是 React 的反模式。
                            【解决方案29】:

                            我使用 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})
                            }
                            

                            用途:

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

                            【讨论】:

                            • a[b] = value 这会改变this.state[attrs[0]] 返回的嵌套状态对象。不要改变当前状态对象。
                            【解决方案30】:

                            这是我的初始状态

                                const initialStateInput = {
                                    cabeceraFamilia: {
                                        familia: '',
                                        direccion: '',
                                        telefonos: '',
                                        email: ''
                                    },
                                    motivoConsulta: '',
                                    fechaHora: '',
                                    corresponsables: [],
                                }
                            

                            钩子或者你可以用状态(类组件)替换它

                            const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);
                            

                            handleChange的方法

                            const actualizarState = e => {
                                const nameObjects = e.target.name.split('.');
                                const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
                                setInfoAgendamiento({...newState});
                            };
                            

                            用嵌套状态设置状态的方法

                            const setStateNested = (state, nameObjects, value) => {
                                let i = 0;
                                let operativeState = state;
                                if(nameObjects.length > 1){
                                    for (i = 0; i < nameObjects.length - 1; i++) {
                                        operativeState = operativeState[nameObjects[i]];
                                    }
                                }
                                operativeState[nameObjects[i]] = value;
                                return state;
                            }
                            

                            最后这是我使用的输入

                            <input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />
                            

                            【讨论】:

                              猜你喜欢
                              • 2020-12-04
                              • 2023-01-31
                              • 2019-12-15
                              • 2018-11-26
                              • 2017-08-08
                              • 1970-01-01
                              • 2018-04-16
                              • 2020-11-13
                              • 1970-01-01
                              相关资源
                              最近更新 更多