小编典典

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

reactjs

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

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

CodeSandbox-组件vs PureComponent和嵌套状态更改

回到这个问题-我在这里关注的是性能以及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,React将对我们传递给它的对象进行 浅层合并
,这意味着它将仅检查顶级属性,在此示例中为isSubmittingand inputs

因此,我们可以将包含这两个顶级属性(isSubmittinginputs)的完整newState对象传递给它,也可以传递这些属性之一,然后将其浅层合并为先前的状态。

问题1

您是否同意最好的做法是仅传递state我们正在更新的顶级属性?例如,如果我们不更新该isSubmitting属性,则应避免将其传递给setState()其他属性,以避免与可能同时与该属性setState()一起排队的其他并发调用发生冲突/覆盖?这样对吗?

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

问题2

从性能角度来看,复制当前状态以更改其嵌套属性的最佳方法是什么?

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

即使您可以这样做:

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

你不应该 因为在React Docs中,我们拥有:

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

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

由于我们正在处理嵌套对象,因此我们需要一种深度复制对象的方法,并假设您不想使用任何第三方库(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()

第二种选择具有更好的性能结果。至少好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
            }
          }
        });
      });
    }
  );
}

阅读 498

收藏
2020-07-22

共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
        }
    }
}))
2020-07-22