Javascript 使用 React useState() 钩子更新和合并状态对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/55342406/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Updating and merging state object using React useState() hook
提问by cbdeveloper
I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
我发现 React Hooks 文档的这两部分有点令人困惑。哪个是使用状态挂钩更新状态对象的最佳实践?
Imagine a want to make the following state update:
想象一下想要进行以下状态更新:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
选项1
From the Using the React Hookarticle, we get that this is possible:
从使用 React Hook文章中,我们了解到这是可能的:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
所以我可以这样做:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
进而:
setMyState({
...myState,
propB: false
});
OPTION 2
选项 2
And from the Hooks Referencewe get that:
从Hooks Reference 中我们得到:
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
与类组件中的 setState 方法不同,useState 不会自动合并更新对象。您可以通过将函数更新程序形式与对象传播语法相结合来复制此行为:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
据我所知,两者都有效。那么区别是什么呢?哪一个是最佳实践?我应该使用传递函数(选项 2)来访问以前的状态,还是应该使用扩展语法(选项 1)简单地访问当前状态?
采纳答案by Tholle
Both options are valid, but just as with setStatein a class component you need to be careful when updating state derived from something that already is in state.
这两个选项都是有效的,但就像setState在类组件中一样,在更新从已经处于状态的事物派生的状态时需要小心。
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
例如,如果您连续两次更新计数,如果您不使用更新状态的功能版本,它将无法按预期工作。
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
回答by UjinT34
The best practice is to use separate calls:
最佳做法是使用单独的调用:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
选项 1 可能会导致更多错误,因为此类代码通常最终位于具有过时值的闭包中myState。
Option 2 should be used when the new state is based on the old one:
当新状态基于旧状态时,应使用选项 2:
setCount(count => count + 1);
For complex state structure consider using useReducer
对于复杂的状态结构,请考虑使用useReducer
For complex structures that share some shape and logic you can create a custom hook:
对于共享某些形状和逻辑的复杂结构,您可以创建自定义钩子:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('[email protected]');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
回答by Shubham Khatri
One or more options regarding state type can be suitable depending on your usecase
根据您的用例,一个或多个关于状态类型的选项可能适用
Generally you could follow the following rules to decide the sort of state that you want
通常,您可以遵循以下规则来决定您想要的状态类型
First: Are the individual states related
第一:各个州是否相关
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useStateso that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
如果您在应用程序中拥有的各个状态彼此相关,那么您可以选择将它们组合在一个对象中。否则最好将它们分开并使用多个,useState以便在处理特定处理程序时您只更新相关状态属性而不关心其他处理程序
For instance, user properties such as name, emailare related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
例如,诸如name, email相关的用户属性,您可以将它们组合在一起,而为了维护多个计数器,您可以使用multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
第二:更新状态的逻辑是否复杂,取决于处理程序或用户交互
In the above case its better to make use of useReducerfor state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, createand deleteelements on different interactions
在上述情况下,最好使用useReducer状态定义。这样的那种情景是,当你试图创建例如与待办事项应用要非常普遍update,create而且delete在不同的互动元素
Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
我应该使用传递函数(选项 2)来访问以前的状态,还是应该使用扩展语法(选项 1)简单地访问当前状态?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
使用钩子的状态更新也是批处理的,因此每当你想根据前一个更新状态时,最好使用回调模式。
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffectbeing called only on initial render when adds a listener that updates state on an event.
当 setter 没有从封闭的闭包中接收到更新的值时,更新状态的回调模式也会派上用场,因为它只被定义了一次。例如,useEffect当添加更新事件状态的侦听器时,仅在初始渲染时调用此情况的示例。
回答by dance2die
Which one is the best practice for updating a state object using the state hook?
哪个是使用状态挂钩更新状态对象的最佳实践?
They are both valid as other answers have pointed out.
正如其他答案所指出的那样,它们都是有效的。
what is the difference?
有什么不同?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
似乎混乱是由于"Unlike the setState method found in class components, useState does not automatically merge update objects",尤其是“合并”部分。
Let's compare this.setState& useState
让我们比较this.setState&useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/Bin togglehandler.
And they both update just one prop passed as e.target.name.
它们都propA/B在toggle处理程序中切换。他们都只更新了一个作为e.target.name.
Check out the difference it makes when you update just one property in setMyState.
看看当您只更新setMyState.
Following demo shows that clicking on propAthrows an error(which occurs setMyStateonly),
以下演示显示单击propA会引发错误(仅发生setMyState),
You can following along
你可以跟着
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
警告:组件正在将复选框类型的受控输入更改为不受控制。输入元素不应从受控切换到不受控制(反之亦然)。决定在组件的生命周期内使用受控或非受控输入元素。
It's because when you click on propAcheckbox, propBvalue is dropped and only propAvalue is toggled thus making propB's checkedvalue as undefined making the checkbox uncontrolled.
这是因为当您单击propA复选框时,propB值被删除并且只有propA值被切换,从而使propB的checked值未定义使复选框不受控制。
And the this.setStateupdates only one property at a time but it mergesother property thus the checkboxes stay controlled.
并且this.setState一次只更新一个属性,但更新merges其他属性,因此复选框保持受控。
I dug thru the source code and the behavior is due to useStatecalling useReducer
我挖掘了源代码,行为是由于useState调用useReducer
Internally, useStatecalls useReducer, which returns whatever state a reducer returns.
在内部,useState调用useReducer,它返回减速器返回的任何状态。
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateStateis the internal implementation for useReducer.
updateState的内部实现在哪里useReducer?
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
如果您熟悉 Redux,您通常会像在选项 1 中那样通过扩展先前状态来返回一个新对象。
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
因此,如果您只设置一个属性,则不会合并其他属性。
回答by SILENT
Both options are valid but they do make a difference. Use Option 1 (setCount(count + 1)) if
这两个选项都是有效的,但它们确实有所不同。使用选项 1 (setCount(count + 1)) 如果
- Property doesn't matter visually when it updates browser
- Sacrifice refresh rate for performance
- Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
- 属性在更新浏览器时在视觉上无关紧要
- 牺牲刷新率换取性能
- 根据事件更新输入状态(即 event.target.value);如果您使用选项 2,它将由于性能原因将 event 设置为 null,除非您有 event.persist() - 请参阅事件池。
Use Option 2 (setCount(c => c + 1)) if
使用选项 2 (setCount(c => c + 1)) 如果
- Property does matter when it updates on the browser
- Sacrifice performance for better refresh rate
- 属性在浏览器上更新时很重要
- 牺牲性能以获得更好的刷新率
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
当一些具有自动关闭功能的警报应该按顺序关闭时,我注意到了这个问题。
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
注意:我没有证明性能差异的统计数据,但它基于关于 React 16 性能优化的 React 会议。
回答by Tom Finney
Both are perfectly fine for that use case. The functional argument that you pass to setStateis only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setStatebut I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
两者都非常适合该用例。您传递给的函数参数setState仅在您想通过比较前一个状态来有条件地设置状态时才真正有用(我的意思是您可以使用围绕调用的逻辑来完成它,setState但我认为它在函数中看起来更清晰)或者如果您在无法立即访问先前状态的最新版本的闭包中设置状态。
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
一个例子是一个事件侦听器,它在安装到窗口时只绑定一次(无论出于何种原因)。例如
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClickwas only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevStatewould only capture the state on initial render and not from any updates. The function argument passed to setStatewould always have access to the most recent iteration of your state.
如果handleClick仅使用选项 1 设置状态,则它看起来像setState({...prevState, new: true }). 但是,这可能会引入一个错误,因为它prevState只会在初始渲染时捕获状态,而不是从任何更新中捕获状态。传递给的函数参数setState将始终可以访问状态的最新迭代。
回答by Maheshvirus
If anyone is searching for useState()hooks update for object
如果有人正在搜索 useState()挂钩更新对象
- Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
***************************
- Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));


