【发布时间】: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 将浅合并我们传递给它的对象,这意味着它只会检查顶级属性,在这个例子中是isSubmitting 和 inputs。
所以我们可以将包含这两个顶级属性(isSubmitting 和inputs)的完整 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
}
}
});
});
}
);
}
【问题讨论】:
-
这篇文章可能对你有所帮助stackoverflow.com/questions/43040721/…
-
请注意,您可以使用Stack Snippets for React questions,这样人们就不必访问外部站点(可能会被阻止)。
标签: javascript json reactjs