React钩子引入了useState来设置组件状态。但是我如何使用钩子来替换下面的回调代码:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
我想在状态更新后做一些事情。
我知道我可以使用useEffect来做额外的事情,但我必须检查之前的状态值,这需要位代码。我正在寻找一个简单的解决方案,可以使用useState挂钩。
React钩子引入了useState来设置组件状态。但是我如何使用钩子来替换下面的回调代码:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
我想在状态更新后做一些事情。
我知道我可以使用useEffect来做额外的事情,但我必须检查之前的状态值,这需要位代码。我正在寻找一个简单的解决方案,可以使用useState挂钩。
当前回答
UseEffect是主要的解决方案。但正如Darryl提到的,使用useEffect并将state作为第二个参数传入有一个缺陷,组件将在初始化过程中运行。如果你只想让回调函数使用更新后的状态值运行,你可以设置一个本地常量,并在setState和回调中使用它。
const [counter, setCounter] = useState(0);
const doSomething = () => {
const updatedNumber = 123;
setCounter(updatedNumber);
// now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
console.log(updatedNumber);
}
其他回答
你的问题很有道理。让我告诉您,useEffect在默认情况下运行一次,并在每次依赖数组更改之后运行一次。
请看下面的例子:
import React,{ useEffect, useState } from "react";
const App = () => {
const [age, setAge] = useState(0);
const [ageFlag, setAgeFlag] = useState(false);
const updateAge = ()=>{
setAgeFlag(false);
setAge(age+1);
setAgeFlag(true);
};
useEffect(() => {
if(!ageFlag){
console.log('effect called without change - by default');
}
else{
console.log('effect called with change ');
}
}, [ageFlag,age]);
return (
<form>
<h2>hooks demo effect.....</h2>
{age}
<button onClick={updateAge}>Text</button>
</form>
);
}
export default App;
如果你想要setState回调与钩子一起执行,那么使用标志变量并在useEffect中给出If ELSE OR If块,以便当条件满足时,只执行该代码块。无论如何,当依赖项数组改变时,效果会运行,但效果中的IF代码只会在特定的条件下执行。
我探索了use-state with-callback npm库和其他类似的自定义钩子,但最后我意识到我可以做这样的事情:
const [user, setUser] = React.useState(
{firstName: 'joe', lastName: 'schmo'}
)
const handleFirstNameChange=(val)=> {
const updatedUser = {
...user,
firstName: val
}
setUser(updatedUser)
updateDatabase(updatedUser)
}
我认为,使用useEffect不是一种直观的方式。
我为此创建了一个包装器。在这个自定义钩子中,你可以将你的回调传递给setState参数,而不是useState参数。
我刚刚创建了Typescript版本。所以如果你需要在Javascript中使用它,只需从代码中删除一些类型符号。
使用
const [state, setState] = useStateCallback(1);
setState(2, (n) => {
console.log(n) // 2
});
宣言
import { SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
type Callback<T> = (value?: T) => void;
type DispatchWithCallback<T> = (value: T, callback?: Callback<T>) => void;
function useStateCallback<T>(initialState: T | (() => T)): [T, DispatchWithCallback<SetStateAction<T>>] {
const [state, _setState] = useState(initialState);
const callbackRef = useRef<Callback<T>>();
const isFirstCallbackCall = useRef<boolean>(true);
const setState = useCallback((setStateAction: SetStateAction<T>, callback?: Callback<T>): void => {
callbackRef.current = callback;
_setState(setStateAction);
}, []);
useEffect(() => {
if (isFirstCallbackCall.current) {
isFirstCallbackCall.current = false;
return;
}
callbackRef.current?.(state);
}, [state]);
return [state, setState];
}
export default useStateCallback;
缺点
如果传递的箭头函数引用了一个变量外部函数,那么它将捕获当前值,而不是状态更新后的值。 在上面的使用示例中,console.log(state)将打印1而不是2。
UseEffect是主要的解决方案。但正如Darryl提到的,使用useEffect并将state作为第二个参数传入有一个缺陷,组件将在初始化过程中运行。如果你只想让回调函数使用更新后的状态值运行,你可以设置一个本地常量,并在setState和回调中使用它。
const [counter, setCounter] = useState(0);
const doSomething = () => {
const updatedNumber = 123;
setCounter(updatedNumber);
// now you can "do something" with updatedNumber and don't have to worry about the async nature of setState!
console.log(updatedNumber);
}
用useEffect模拟setState回调,只在状态更新时触发(不是初始状态):
const [state, setState] = useState({ name: "Michael" })
const isFirstRender = useRef(true)
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false // toggle flag after first render/mounting
return;
}
console.log(state) // do something after state has updated
}, [state])
自定义钩子useEffectUpdate
function useEffectUpdate(callback) {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false; // toggle flag after first render/mounting
return;
}
callback(); // performing action after state has updated
}, [callback]);
}
// client usage, given some state dep
const cb = useCallback(() => { console.log(state) }, [state]); // memoize callback
useEffectUpdate(cb);