用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?

Posted

技术标签:

【中文标题】用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?【英文标题】:What's the best alternative to update nested React state property with setState()?用 setState() 更新嵌套的 React 状态属性的最佳选择是什么? 【发布时间】:2019-06-23 23:55:12 【问题描述】:

我一直在考虑在这些选项中使用 React setState() 方法更新嵌套属性的最佳方法是什么。考虑到性能并避免与其他可能的并发状态更改可能发生冲突,我也愿意接受更有效的方法。

注意:我正在使用扩展React.Component 的类组件。如果您使用React.PureComponent,则在更新嵌套属性时必须格外小心,因为如果您不更改state 的任何***属性,则可能不会触发重新渲染。这是一个说明此问题的沙盒:

CodeSandbox - Component vs PureComponent and nested state changes

回到这个问题 - 我关心的是在更新状态的嵌套属性时其他并发 setState() 调用之间的性能和可能的冲突:

示例:

假设我正在构建一个表单组件,我将使用以下对象初始化我的表单状态:

this.state = 
  isSubmitting: false,
  inputs: 
    username: 
      touched: false,
      dirty: false,
      valid: false,
      invalid: false,
      value: 'some_initial_value'
    ,
    email: 
      touched: false,
      dirty: false,
      valid: false,
      invalid: false,
      value: 'some_initial_value'
    
  

根据我的研究,通过使用setState(),React 将浅合并我们传递给它的对象,这意味着它只会检查***属性,在这个例子中是isSubmittinginputs

所以我们可以将包含这两个***属性(isSubmittinginputs)的完整 newState 对象传递给它,或者我们可以传递其中一个属性并将其浅合并到先前的状态。

问题 1

您是否同意最好只传递我们正在更新的 state ***属性?例如,如果我们不更新isSubmitting 属性,我们应该避免将其传递给setState(),以避免与其他可能与此一起排队的setState() 并发调用发生冲突/覆盖?它是否正确?

在此示例中,我们将传递一个仅具有 inputs 属性的对象。这将避免与可能试图更新isSubmitting 属性的另一个setState() 发生冲突/覆盖。

问题 2

在性能方面,复制当前状态以更改其嵌套属性的最佳方式是什么?

在这种情况下,假设我想设置state.inputs.username.touched = true

即使你可以这样做:

this.setState( (state) => 
  state.inputs.username.touched = true;
  return state;
);

你不应该。因为,从React Docs,我们有:

state 是对更改时组件状态的引用 被应用。它不应该直接突变。相反,改变 应该由根据来自的输入构建一个新对象来表示 状态和道具。

所以,从上面的摘录中我们可以推断出我们应该从当前的state 对象构建一个新对象,以便根据需要对其进行更改和操作,并将其传递给setState() 以更新@987654347 @。

由于我们正在处理嵌套对象,我们需要一种方法来深度复制对象,并且假设您不想使用任何 3rd 方库 (lodash) 这样做,我想出的是:

this.setState( (state) => 
  let newState = JSON.parse(JSON.stringify(state));
  newState.inputs.username.touched = true;
  return (
    inputs: newState.inputs
  );
);

请注意,当您的state 具有嵌套对象时,您也不应该使用let newState = Object.assign(,state)。因为这会浅拷贝state 嵌套对象引用,因此您仍然会直接改变状态,因为newState.inputs === state.inputs === this.state.inputs 是真的。它们都将指向同一个对象inputs

但由于JSON.parse(JSON.stringify(obj)) 有其性能限制,并且还有一些数据类型或循环数据可能不适合 JSON,您会推荐什么其他方法来深度复制嵌套对象以更新它?

我想出的另一个解决方案如下:

this.setState( (state) => 
  let usernameInput = ;      
  usernameInput['username'] = Object.assign(,state.inputs.username);
  usernameInput.username.touched = true;
  let newInputs = Object.assign(,state.inputs,usernameInput);

  return(
    inputs: newInputs
  );
;

我在第二个备选方案中所做的是从要更新的最里面的对象(在本例中为 username 对象)创建一个新对象。我必须在键username 中获取这些值,这就是我使用usernameInput['username'] 的原因,因为稍后我会将它合并到newInputs 对象中。一切都使用Object.assign() 完成。

第二个选项变得更好performance results。至少好 50%。

关于这个主题还有其他想法吗?很抱歉这个问题很长,但我认为它很好地说明了问题。

编辑:我从以下答案中采用的解决方案:

我的 TextInput 组件 onChange 事件监听器(我通过 React Context 提供服务):

onChange=this.context.onChange(this.props.name)

我的表单组件中的 onChange 函数

onChange(inputName) 
  return(

    (event) => 
      event.preventDefault();
      const newValue = event.target.value;

      this.setState( (prevState) => 

        return(
          inputs: 
            ...prevState.inputs,
            [inputName]: 
              ...prevState.inputs[inputName],
              value: newValue
            
          
        );
      );
    
  );

【问题讨论】:

这篇文章可能对你有所帮助***.com/questions/43040721/… 请注意,您可以使用Stack Snippets for React questions,这样人们就不必访问外部站点(可能会被阻止)。 【参考方案1】:

我可以想到其他几种方法来实现它。

解构每一个嵌套元素,只覆盖正确的元素:

this.setState(prevState => (
    inputs: 
        ...prevState.inputs,
        username: 
            ...prevState.inputs.username,
            touched: true
        
    
))

使用解构运算符复制您的输入:

this.setState(prevState => 
    const inputs = ...prevState.inputs;
    inputs.username.touched = true;
    return  inputs 
)

编辑

使用计算属性的第一个解决方案:

this.setState(prevState => (
    inputs: 
        ...prevState.inputs,
        [field]: 
            ...prevState.inputs.[field],
            [action]: value
        
    
))

【讨论】:

你将如何使用动态键进行解构? inputs: ...prevState.inputs, [inputName]: ...prevState.inputs[inputName], value: newValue 通过使用计算属性:developer.mozilla.org/en-US/docs/Web/javascript/Reference/…. 当我到达...prevState.inputs[inputName]时遇到错误 错误是什么?您确定 inputName 是有效的属性名称吗?另外,投反对票的人(这里的每个答案)可以解释什么 我只是设法让它在 return 语句中明确指定它工作。我将把它添加到我的问题中。您的解决方案比我的更具可读性和明确性。我会在我的表格中采用它。谢谢!【参考方案2】:

我创建了一个使用动态键更新嵌套状态的 util 函数。

 function _recUpdateState(state, selector, newval) 
          if (selector.length > 1) 
            let field = selector.shift();
            let subObject = ;
            try 
              //Select the subobject if it exists
              subObject =  ..._recUpdateState(state[field], selector, newval) ;
             catch 
              //Create the subobject if it doesn't exist
              subObject = 
                ..._recUpdateState(state, selector, newval)
              ;
            
            return  ...state, [field]: subObject ;
           else 
            let updatedState = ;
            updatedState[selector.shift()] = newval;
            return  ...state, ...updatedState ;
          
        
        
        function updateState(state, selector, newval, autoAssign = true) 
          let newState = _recUpdateState(state, selector, newval);
          if (autoAssign) return Object.assign(state, newState);
          return newState;
        
        
// Example

let initState = 
       sub1: 
          val1: "val1",
          val2: "val2",
          sub2: 
             other: "other value",
             testVal: null
          
       
     
    
    console.log(initState)
    updateState(initState, ["sub1", "sub2", "testVal"], "UPDATED_VALUE")
    console.log(initState)

您传递一个状态以及一个键选择器列表和新值。

您还可以将 autoAssign 值设置为 false 以返回一个对象,该对象是旧状态的副本,但具有新的更新字段 - 否则 autoAssign = true 并更新以前的状态。

最后,如果选择器序列没有出现在对象中,则将创建一个对象和所有具有这些键的嵌套对象。

【讨论】:

【参考方案3】:

你可以试试嵌套Object.Assign:

 const newState = Object.assign(, state, 
  inputs: Object.assign(, state.inputs, 
    username: Object.assign(, state.inputs.username,  touched: true ),
  ),
);

;

你也可以使用扩展运算符:


  ...state,
  inputs: 
    ...state.inputs,
      username: 
      ...state.inputs.username,
      touched: true
   

这是更新嵌套属性并保持状态不可变的正确方法。

【讨论】:

这对我也有帮助! @cbdev420 如果是这样,您可以将我的答案标记为正确,干杯:)【参考方案4】:

使用扩展运算符

  let foo = this.state;

  foo = 
    ...foo,
    bar: baz
  

  this.setState(
    foo
  )

【讨论】:

以上是关于用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState。 React 限制嵌套更新的数量以防止无限循环

React 的 setState(),嵌套结构的数据变异,为啥不直接修改状态呢?

React setState - 将数组添加到具有多个数组的嵌套对象

react学习

React 组件循环更新 (GraphQL)

React - setState(...): 只能更新一个挂载或挂载的组件