我一直在考虑使用React setState()方法在这些选项中更新嵌套属性的最佳方法是什么。我也愿意考虑更高效的方法来考虑性能,并避免与其他可能的并发状态更改发生冲突。
注意: 我使用的是extends的类组件React.Component。如果您使用React.PureComponent,则在更新嵌套属性时必须格外小心,因为如果您不更改的任何顶级属性,这可能不会触发重新渲染state。这是说明此问题的沙箱:
React.Component
React.PureComponent
state
CodeSandbox-组件vs PureComponent和嵌套状态更改
回到这个问题-我在这里关注的是性能以及setState()在状态上更新嵌套属性时其他并发调用之间可能存在的冲突:
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,React将对我们传递给它的对象进行 浅层合并 ,这意味着它将仅检查顶级属性,在此示例中为isSubmittingand inputs。
isSubmitting
inputs
因此,我们可以将包含这两个顶级属性(isSubmitting和inputs)的完整newState对象传递给它,也可以传递这些属性之一,然后将其浅层合并为先前的状态。
问题1
您是否同意最好的做法是仅传递state我们正在更新的顶级属性?例如,如果我们不更新该isSubmitting属性,则应避免将其传递给setState()其他属性,以避免与可能同时与该属性setState()一起排队的其他并发调用发生冲突/覆盖?这样对吗?
在此示例中,我们将仅传递具有inputs属性的对象。这样可以避免与其他setState()可能试图更新该isSubmitting属性的冲突/覆盖。
问题2
从性能角度来看,复制当前状态以更改其嵌套属性的最佳方法是什么?
在这种情况下,想象我要设置state.inputs.username.touched = true。
state.inputs.username.touched = true
即使您可以这样做:
this.setState( (state) => { state.inputs.username.touched = true; return state; });
你不应该 因为在React Docs中,我们拥有:
状态 是在应用更改时对组件状态的引用。它不应该直接突变。相反,更改应通过 根据状态和道具的输入来构建新对象 来表示 。
因此,从上面的摘录中我们可以推断出我们应该从当前state对象中构建一个新对象,以便对其进行更改并根据需要对其进行操作,并传递给它setState()以更新state。
由于我们正在处理嵌套对象,因此我们需要一种深度复制对象的方法,并假设您不想使用任何第三方库(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。
let newState = Object.assign({},state)
newState.inputs === state.inputs === this.state.inputs
但是,由于JSON.parse(JSON.stringify(obj))存在性能限制,并且还存在一些可能不支持JSON的数据类型或循环数据,因此,您建议采用什么其他方法深度复制嵌套对象以进行更新?
JSON.parse(JSON.stringify(obj))
我想出的另一个解决方案是:
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()。
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 } } }))