我一直在思考在这些选项中使用 React setState() 方法更新嵌套属性的最佳方法是什么。考虑到性能并避免与其他可能的并发状态更改发生冲突,我也对更有效的方法持开放态度。
注意:我正在使用一个扩展React.Component
. 如果您正在使用React.PureComponent
,则在更新嵌套属性时必须格外小心,因为如果您不更改state
. 这是说明此问题的沙箱:
CodeSandbox - 组件与 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
您是否同意仅传递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是对应用更改时组件状态的引用。它不应该被直接变异。相反,应该通过基于 state 和 props 的输入构建一个新对象来表示更改。
因此,从上面的摘录我们可以推断,我们应该从当前state
对象构建一个新对象,以便根据需要更改和操作它,并将其传递给以setState()
更新state
.
由于我们正在处理嵌套对象,我们需要一种方法来深度复制对象,并且假设您不想使用任何 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
对象)创建一个新对象。我必须在 key 中获取这些值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
}
}
});
});
}
);
}