React hooks引入useState
来设置组件状态。但是我该如何使用hooks来替换下面的回调函数:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
状态更新后我想做一些事情。
我知道我可以使用 useEffect
来完成额外的操作,但是我需要检查前一个状态值,这需要写一些代码。我正在寻找一个简单的解决方案,可以与 useState
钩子一起使用。
React hooks引入useState
来设置组件状态。但是我该如何使用hooks来替换下面的回调函数:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
状态更新后我想做一些事情。
我知道我可以使用 useEffect
来完成额外的操作,但是我需要检查前一个状态值,这需要写一些代码。我正在寻找一个简单的解决方案,可以与 useState
钩子一起使用。
简单的解决方案,只需安装
npm i use-state-with-callback
import React from 'react';
import { useStateWithCallbackLazy } from "use-state-with-callback";
const initialFilters = {
smart_filter: "",
};
const MyCallBackComp = () => {
const [filters, setFilters] = useStateWithCallbackLazy(initialFilters);
const filterSearchHandle = (e) => {
setFilters(
{
...filters,
smart_filter: e,
},
(value) => console.log("smartFilters:>", value)
);
};
return (
<Input
type="text"
onChange={(e) => filterSearchHandle(e.target.value)}
name="filter"
placeholder="Search any thing..."
/>
);
};
在这里使用Promise似乎仍然会推迟重新渲染后的执行,触发setState
两次可能是获取最新状态的最佳解决方案。因为setState将被列出,我们只需要在重新渲染之前获取prevState
来使用。
我刚刚想到,如果我们可以在这里使用一个Promise
,让setState变得可等待。
这是我的实验结果,感觉比使用回调函数更好。
主要是在useEffect
中暂时使用resolve函数来触发。
function useAsyncState(initialState) {
const [state, setState] = useState(initialState)
const resolveCb = useRef()
const handleSetState = (updatedState) => new Promise((resolve, reject) => {
// force previous promise resolved
if (typeof resolveCb.current === 'function') {
resolveCb.current(updatedState)
}
resolveCb.current = resolve
try {
setState(updatedState)
} catch(err) {
resolveCb.current = undefined
reject(err)
}
})
useEffect(() => {
if (typeof resolveCb.current === 'function') {
resolveCb.current(state)
resolveCb.current = undefined
}
}, [state])
return [state, handleSetState]
}
在组件中使用
function App() {
const [count, setCount] = useAsyncState(0)
const increment = useMemoizedFn(async () => {
const newCount = await setCount(count + 1)
console.log(newCount)
})
console.log('rerender')
return (
<div>
<h3 onClick={increment}>Hi, {count}</h3>
</div>
)
}
我有一个非常具体的用例,需要在dom中渲染一个类,然后设置另一个类。这是我找到的相当优雅的解决方案。
const [value1, setValue1] = useState({value: 'whatever', onValue: false})
useEffect(() => {
setValue1(prev => ({
value: 'whatever',
onValue: !prev.onValue,
}));
}, ['whatever'])
useEffect(() => {
// if you want to ensure the render happens before doThing2() then put it in a timeout of 1ms,
setTimeout(doThing2, 1);
// or if you are happy to call it immediately after setting value don't include the timeout
doThing2()
}, [value1.onValue])
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;
我认为使用 useRef 区分是否已挂载并不是一个好的方式,更好的方法是通过在 useEffect() 中确定 useState() 生成的值是否为初始值来实现。
const [val, setVal] = useState(null)
useEffect(() => {
if (val === null) return
console.log('not mounted, val updated', val)
}, [val])
const [user, setUser] = React.useState(
{firstName: 'joe', lastName: 'schmo'}
)
const handleFirstNameChange=(val)=> {
const updatedUser = {
...user,
firstName: val
}
setUser(updatedUser)
updateDatabase(updatedUser)
}
useEffect
或其中一个使用 useEffect
的自定义钩子。
希望提到这一点,因为这可能会让其他人感到困惑。 - Can RauuseState
:import { useCallback, useEffect, useRef, useState } from 'react';
// Define a generic function type for the updater and the callback
type Updater<T> = T | ((prevState: T) => T);
type Callback<T> = (state: T) => void;
function useStateCallback<T>(initialState: T): [T, (stateUpdater: Updater<T>, cb?: Callback<T>) => void] {
const [state, setState] = useState<T>(initialState);
const cbRef = useRef<Callback<T> | undefined>(undefined); // Ref to hold the callback
const setStateCallback = useCallback(
(stateUpdater: Updater<T>, cb?: Callback<T>) => {
cbRef.current = cb; // Store the callback in ref
// Set the state, handle function type updater for prevState
setState(prevState => typeof stateUpdater === 'function'
? (stateUpdater as (prevState: T) => T)(prevState)
: stateUpdater);
},
[]
);
// useEffect to call the callback after state update
useEffect(() => {
if (cbRef.current) {
cbRef.current(state); // Call the callback with the updated state
cbRef.current = undefined; // Reset the callback ref
}
}, [state]);
return [state, setStateCallback];
}
export default useStateCallback;
import React from 'react';
import useStateCallback from './useStateCallback';
const ExampleComponent: React.FC = () => {
const [count, setCount] = useStateCallback(0);
// Example usage of setState with a callback
const incrementAndLog = () => {
setCount(count + 1, (newCount) => {
console.log(`Count updated to: ${newCount}`);
});
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementAndLog}>Increment</button>
</div>
);
};
export default ExampleComponent;
如果您不需要异步更新状态,可以使用 ref 来保存值,而不是使用 useState
。
const name = useRef("John");
name.current = "Michael";
console.log(name.current); // will print "Michael" since updating the ref is not async
在我们拥有原生内置的setState回调支持之前,我们可以采用纯JavaScript的方式...直接调用函数并将新变量直接传递给它。
const [counter, setCounter] = useState(0);
const doSomething = () => {
const newCounter = 123
setCounter(newCounter);
doSomethingWCounter(newCounter);
};
function doSomethingWCounter(newCounter) {
console.log(newCounter); // 123
}
传递一个函数怎么样?
const [name, setName] = useState(initialName);
...
setName(() => {
const nextName = "Michael";
console.log(nextName);
return nextName;
});