用setState()更新嵌套的React状态属性的最佳选择是什么? [英] What's the best alternative to update nested React state property with setState()?

查看:96
本文介绍了用setState()更新嵌套的React状态属性的最佳选择是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

注意:我正在使用扩展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将浅合并我们传递给它的对象,这意味着它仅用于检查顶级属性,在此示例中为isSubmittinginputs.

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

问题1

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

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

问题2

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

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

即使您可以执行以下操作:

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

不应该.因为从反应文档中,我们可以做到:

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

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

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

解决方案

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

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

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
        }
    }
}))

I've been thinking about what would be the best way among these options to update a nested property using React setState() method. I'm also opened to more efficient methods considering performance and avoiding possible conflicts with other possible concurrent state changes.

Note: I'm using a class component that extends React.Component. If you're using React.PureComponent you must be extra careful when updating nested properties because that might not trigger a re-render if you don't change any top-level property of your state. Here's a sandbox illustrating this issue:

CodeSandbox - Component vs PureComponent and nested state changes

Back to this question - My concern here is about performance and possible conflicts between other concurrent setState() calls when updating a nested property on state:

Example:

Let's say I'm building a form component and I will initialize my form state with the following object:

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'
    }
  }
}

From my research, by using setState(), React will shallow merge the object that we pass to it, which means that it's only going to check the top level properties, which in this example are isSubmitting and inputs.

So we can either pass it a full newState object containing those two top-level properties (isSubmitting and inputs), or we can pass one of those properties and that will be shallow merged into the previous state.

QUESTION 1

Do you agree that it is best practice to pass only the state top-level property that we are updating? For example, if we are not updating the isSubmitting property, we should avoid passing it to setState() in other to avoid possible conflicts/overwrites with other concurrent calls to setState() that might have been queued together with this one? Is this correct?

In this example, we would pass an object with only the inputs property. That would avoid conflict/overwrite with another setState() that might be trying to update the isSubmitting property.

QUESTION 2

What is the best way, performance-wise, to copy the current state to change its nested properties?

In this case, imagine that I want to set state.inputs.username.touched = true.

Even though you could do this:

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

You shouldn't. Because, from React Docs, we have that:

state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.

So, from the excerpt above we can infer that we should build a new object from the current state object, in order to change it and manipulate it as we want and pass it to setState() to update the state.

And since we are dealing with nested objects, we need a way to deep copy the object, and assuming you don't want to use any 3rd party libraries (lodash) to do so, what I've come up with was:

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

Note that when your state has nested object you also shouldn't use let newState = Object.assign({},state). Because that would shallow copy the state nested object reference and thus you would still be mutating state directly, since newState.inputs === state.inputs === this.state.inputs would be true. All of them would point to the same object inputs.

But since JSON.parse(JSON.stringify(obj)) has its performance limitations and also there are some data types, or circular data, that might not be JSON-friendly, what other approach would you recommend to deep copy the nested object in order to update it?

The other solution I've come up with is the following:

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
  });
};

What I did in this second alternative was to create an new object from the innermost object that I'm going to update (which in this case is the username object). And I have to get those values inside the key username, and that's why I'm using usernameInput['username'] because later I will merge it into a newInputs object. Everything is done using Object.assign().

This second option has gotten better performance results. At least 50% better.

Any other ideas on this subject? Sorry for the long question but I think it illustrates the problem well.

EDIT: Solution I've adopted from answers below:

My TextInput component onChange event listener (I'm serving it through React Context):

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

My onChange function inside my Form Component

onChange(inputName) {
  return(

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

      this.setState( (prevState) => {

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

解决方案

I can think of a few other ways to achieve it.

Deconstructing every nested element and only overriding the right one :

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

Using the deconstructing operator to copy your inputs :

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


EDIT

First solution using computed properties :

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

这篇关于用setState()更新嵌套的React状态属性的最佳选择是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆