用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?
Posted
技术标签:
【中文标题】用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?【英文标题】:What's the best alternative to update nested React state property with setState()?用 setState() 更新嵌套的 React 状态属性的最佳选择是什么? 【发布时间】: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
);
);
);
【问题讨论】:
这篇文章可能对你有所帮助***.com/questions/43040721/… 请注意,您可以使用Stack Snippets for React questions,这样人们就不必访问外部站点(可能会被阻止)。 【参考方案1】:我可以想到其他几种方法来实现它。
解构每一个嵌套元素,只覆盖正确的元素:
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
))
【讨论】:
你将如何使用动态键进行解构?inputs: ...prevState.inputs, [inputName]: ...prevState.inputs[inputName], value: newValue
通过使用计算属性:developer.mozilla.org/en-US/docs/Web/javascript/Reference/….
当我到达...prevState.inputs[inputName]
时遇到错误
错误是什么?您确定 inputName
是有效的属性名称吗?另外,投反对票的人(这里的每个答案)可以解释什么
我只是设法让它在 return 语句中明确指定它工作。我将把它添加到我的问题中。您的解决方案比我的更具可读性和明确性。我会在我的表格中采用它。谢谢!【参考方案2】:
我创建了一个使用动态键更新嵌套状态的 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 并更新以前的状态。
最后,如果选择器序列没有出现在对象中,则将创建一个对象和所有具有这些键的嵌套对象。
【讨论】:
【参考方案3】:你可以试试嵌套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
这是更新嵌套属性并保持状态不可变的正确方法。
【讨论】:
这对我也有帮助! @cbdev420 如果是这样,您可以将我的答案标记为正确,干杯:)【参考方案4】:使用扩展运算符
let foo = this.state;
foo =
...foo,
bar: baz
this.setState(
foo
)
【讨论】:
以上是关于用 setState() 更新嵌套的 React 状态属性的最佳选择是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState。 React 限制嵌套更新的数量以防止无限循环
React 的 setState(),嵌套结构的数据变异,为啥不直接修改状态呢?