Javascript 什么';使用setState()更新嵌套的React state属性的最佳替代方法是什么?

Javascript 什么';使用setState()更新嵌套的React state属性的最佳替代方法是什么?,javascript,json,reactjs,Javascript,Json,Reactjs,我一直在考虑使用React setState()方法更新嵌套属性的最佳方法是什么。考虑到性能和避免与其他可能的并发状态更改可能发生的冲突,我也愿意采用更有效的方法 注意:我使用的类组件扩展了React.component。如果使用的是React.PureComponent,则在更新嵌套属性时必须格外小心,因为如果不更改状态的任何顶级属性,这可能不会触发重新渲染。这里有一个沙箱来说明这个问题: 回到这个问题-我在这里关心的是在更新状态上的嵌套属性时,其他并发setState()调用之间的性能和可

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

注意:我使用的类组件扩展了
React.component
。如果使用的是
React.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将浅层合并我们传递给它的对象,这意味着它只检查顶级属性,在本例中是
isSubmitting
inputs

因此,我们可以向它传递一个包含这两个顶级属性(
isSubmitting
inputs
)的完整newState对象,也可以传递其中一个属性,并将其合并到前一个状态

问题1

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

在本例中,我们将传递一个仅具有
输入
属性的对象。这将避免与另一个试图更新
isSubmitting
属性的
setState()
冲突/覆盖

问题2

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

在本例中,假设我想要设置
state.inputs.username.touch=true

即使你可以这样做:

this.setState( (state) => {
  state.inputs.username.touched = true;
  return state;
});
你不应该。因为,从,我们有:

状态是对更改发生时组件状态的引用 正在应用。它不应该直接变异。取而代之的是变化 应通过基于来自的输入构建新对象来表示 状态和道具。

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

由于我们处理的是嵌套对象,我们需要一种深度复制对象的方法,并且假设您不想使用任何第三方库(lodash)来执行此操作,我想到的是:

this.setState( (state) => {
  let newState = JSON.parse(JSON.stringify(state));
  newState.inputs.username.touched = true;
  return ({
    inputs: newState.inputs
  });
});
请注意,当您的
状态
具有嵌套对象时,您也不应该使用
让newState=object.assign({},state)
。因为这将浅复制
状态
嵌套对象引用,因此您仍将直接改变状态,因为
newState.inputs==state.inputs==this.state.inputs
将为true。它们都指向同一个对象
输入

但是,既然
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上下文为其提供服务):

表单组件中的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
            }
          }
        });
      });
    }
  );
}

您可以尝试使用嵌套的
对象。分配

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

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

我可以想出一些其他方法来实现它

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

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

使用扩展运算符

  let {foo} = this.state;

  foo = {
    ...foo,
    bar: baz
  }

  this.setState({
    foo
  })

我制作了一个util函数,用动态键更新嵌套状态

function\u重新更新状态(状态、选择器、newval){
如果(selector.length>1){
let field=selector.shift();
设子对象={};
试一试{
//选择子对象(如果存在)
子对象={…\u recUpdateState(state[field],selector,newval)};
}抓住{
//如果子对象不存在,则创建该子对象
子对象={
…_recUpdateState(状态、选择器、newval)
  let {foo} = this.state;

  foo = {
    ...foo,
    bar: baz
  }

  this.setState({
    foo
  })