我发现这两个React Hooks文档有些混乱。使用状态挂钩更新状态对象的最佳做法是哪种?
想象一下要进行以下状态更新:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
选项1
从Using the React Hook文章中,我们发现这是可能的:
const [count, setCount] = useState(0);
setCount(count + 1);
所以我可以做:
const [myState, setMyState] = useState(INITIAL_STATE);
然后:
setMyState({
...myState,
propB: false
});
选项2
从Hooks Reference我们得到:
与在类组件中找到的setState方法不同,useState可以 不会自动合并更新对象。您可以复制此 通过将功能更新程序形式与对象传播相结合来实现行为 语法:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
据我所知,两者都有效。那么区别是什么呢?最佳做法是哪一种?我应该使用传递函数(选项2)来访问先前的状态,还是应该简单地使用扩展语法来访问当前状态(选项1)?
答案 0 :(得分:8)
这两个选项都是有效的,但是就像在类组件中使用setState
一样,在更新从已经存在的状态中派生的状态时,也需要小心。
例如连续两次更新计数,如果不使用更新状态的函数版本,它将无法按预期工作。
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>
答案 1 :(得分:5)
如果有人在搜索 useState(),则挂钩 object
的更新- 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'
}));
答案 2 :(得分:3)
最佳做法是使用单独的呼叫:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
选项1可能会导致更多错误,因为这样的代码通常以闭包结尾,闭包的值为myState
。
当新状态基于旧状态时,应使用选项2:
setCount(count => count + 1);
对于复杂的状态结构,请考虑使用useReducer
对于共享某些形状和逻辑的复杂结构,您可以创建一个自定义钩子:
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('some@mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
答案 3 :(得分:3)
有关状态类型的一个或多个选项可能适用,具体取决于您的用例
通常,您可以遵循以下规则来确定所需的状态
首先:各个州是否相关
如果您在应用程序中具有的各个状态彼此关联,则可以选择将它们分组在一起在一个对象中。否则,最好将它们分开并使用多个useState
,以便在处理特定处理程序时,您仅更新相对状态属性,而不关心其他状态
例如,诸如name, email
之类的用户属性是相关的,您可以将它们分组在一起,而要维护多个计数器,则可以使用multiple useState hooks
第二个:更新状态的逻辑是否复杂并且取决于处理程序或用户交互
在上述情况下,最好使用useReducer
进行状态定义。当您尝试创建例如todo应用程序并希望在不同交互中update
,create
和delete
元素的情况下,这种情况非常普遍。
使用挂钩的我应该使用传递功能(选项2)来访问上一个 状态,还是我应该简单地使用扩展语法访问当前状态 (选项1)?
状态更新也会被批量处理,因此,每当您要基于前一个状态更新状态时,最好使用回调模式。
当设置器由于仅定义一次而没有从封闭的闭包中接收到更新的值时,用于更新状态的回调模式也将派上用场。例如,在添加更新事件状态的侦听器时仅在初始渲染上调用useEffect
的情况。
答案 4 :(得分:1)
哪种是使用状态挂钩更新状态对象的最佳实践?
正如其他答案所指出的,它们都是有效的。
有什么区别?
似乎造成混淆的原因是"Unlike the setState method found in class components, useState does not automatically merge update objects"
,尤其是“合并”部分。
让我们比较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] });
}
...
}
它们两者都在propA/B
处理程序中切换toggle
。
而且他们都只更新了一个通过e.target.name
传递的道具。
检查一下您仅更新setMyState
中的一个属性时的区别。
以下演示显示单击propA
会引发错误(仅发生setMyState
),
您可以关注
警告:组件正在将类型为复选框的受控输入更改为不受控制。输入元素不应从受控状态切换到非受控状态(反之亦然)。在组件的使用寿命中决定使用受控还是不受控制的输入元素。
这是因为当您单击propA
复选框时,propB
的值将被删除,并且仅切换propA
的值,从而使propB
的{{1}}的值成为undefined,使复选框不受控制。
checked
一次仅更新一个属性,而this.setState
则更新另一个属性,因此复选框保持受控状态。
我通过源代码进行了挖掘,其行为是由于merges
调用了useState
在内部,useReducer
调用useState
,它返回减速器返回的任何状态。
useReducer
其中 useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
是updateState
的内部实现。
useReducer
如果您熟悉Redux,通常可以像在选项1中那样,通过扩展先前的状态来返回新对象。
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;
}
},
因此,如果您仅设置一个属性,则其他属性不会合并。
答案 5 :(得分:1)
两个选项均有效,但确实有所不同。 如果
使用选项1(setCount(count + 1))在以下情况下使用选项2(setCount(c => c + 1))
当一些具有自动关闭功能的警报应分批关闭时,我注意到了这个问题。
注意:我没有统计数据可以证明性能上的差异,但是它基于关于React 16性能优化的React会议。
答案 6 :(得分:0)
我要提出的解决方案更简单、更容易,不会比上面的解决方案搞砸,并且用法与 useState
API 相同.
使用 npm 包 use-merge-state
(here)。将其添加到您的依赖项中,然后像这样使用它:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
希望对大家有帮助。 :)
答案 7 :(得分:-1)
对于该用例,两者都很好。传递给setState
的函数参数仅在您希望通过区分前一个状态有条件地设置状态时才非常有用(我的意思是,您可以使用围绕setState
的调用的逻辑来做到这一点,但我认为它在函数中看起来更干净),或者如果您在无法立即访问先前状态的最新版本的闭包中设置状态,则为
一个例子是类似于事件监听器的东西,由于某种原因,它在安装到窗口时仅绑定一次(无论出于何种原因)。例如
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
如果handleClick
仅使用选项1设置状态,则看起来像setState({...prevState, new: true })
。但是,这可能会引入错误,因为prevState
仅会在初始渲染时捕获状态,而不会从任何更新中捕获状态。传递给setState
的function参数始终可以访问您状态的最新迭代。