[英]What's the best alternative to update nested React state property with setState()?
我一直在考虑在这些选项中使用 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 将浅合并我们传递给它的对象,这意味着它只会检查顶级属性,在这个例子中是isSubmitting
和inputs
。
因此,我们可以向它传递一个完整的 newState 对象,该对象包含这两个顶级属性( isSubmitting
和inputs
),或者我们可以传递其中一个属性并将其浅合并到前一个状态中。
问题 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 和 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
对象)创建一个新对象。 我必须在键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
}
}
}))
您可以尝试使用嵌套的Object.Assign
:
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
}
}
这是更新嵌套属性并保持状态不可变的正确方法。
我制作了一个使用动态键更新嵌套状态的 util 函数。
function _recUpdateState(state, selector, newval) { if (selector.length > 1) { let field = selector.shift(); let subObject = {}; try { //Select the subobject if it exists subObject = { ..._recUpdateState(state[field], selector, newval) }; } catch { //Create the subobject if it doesn't exist subObject = { ..._recUpdateState(state, selector, newval) }; } return { ...state, [field]: subObject }; } else { let updatedState = {}; updatedState[selector.shift()] = newval; return { ...state, ...updatedState }; } } function updateState(state, selector, newval, autoAssign = true) { let newState = _recUpdateState(state, selector, newval); if (autoAssign) return Object.assign(state, newState); return newState; } // Example let initState = { sub1: { val1: "val1", val2: "val2", sub2: { other: "other value", testVal: null } } } console.log(initState) updateState(initState, ["sub1", "sub2", "testVal"], "UPDATED_VALUE") console.log(initState)
您传递一个状态以及一个键选择器列表和新值。
您还可以将 autoAssign 值设置为 false 以返回一个对象,该对象是旧状态的副本,但具有新的更新字段 - 否则 autoAssign = true 并更新前一个状态。
最后,如果选择器序列没有出现在对象中,则将创建一个对象和所有具有这些键的嵌套对象。
使用扩展运算符
let {foo} = this.state;
foo = {
...foo,
bar: baz
}
this.setState({
foo
})
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.