setState(updater, callback) for useState
接下来的实现非常接近类的原始setState回调。
对已接受答案的改进:
回调执行在初始渲染时被省略了——我们只希望在状态更新时调用它
对于每个setState调用,回调可以是动态的,就像类一样
使用
const App = () => {
const [state, setState] = useStateCallback(0); // same API as useState
const handleClick = () => {
setState(
prev => prev + 1,
// second argument is callback, `s` being the *updated* state
s => console.log("I am called after setState, state:", s)
);
};
return <button onClick={handleClick}>Increment</button>;
}
useStateCallback
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null); // init mutable ref container for callbacks
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `null` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
TypeScript version
function useStateCallback<T>(
initialState: T
): [T, (state: T, cb?: (state: T) => void) => void] {
const [state, setState] = useState(initialState);
const cbRef = useRef<((state: T) => void) | undefined>(undefined); // init mutable ref container for callbacks
const setStateCallback = useCallback((state: T, cb?: (state: T) => void) => {
cbRef.current = cb; // store current, passed callback in ref
setState(state);
}, []); // keep object reference stable, exactly like `useState`
useEffect(() => {
// cb.current is `undefined` on initial render,
// so we only invoke callback on state *updates*
if (cbRef.current) {
cbRef.current(state);
cbRef.current = undefined; // reset callback after execution
}
}, [state]);
return [state, setStateCallback];
}
进一步信息:React Hooks常见问题解答:是否有类似实例变量的东西?
工作示例
const App = () => {
const [state, setState] = useStateCallback(0);
const handleClick = () =>
setState(
prev => prev + 1,
// important: use `s`, not the stale/old closure value `state`
s => console.log("I am called after setState, state:", s)
);
return (
<div>
<p>Hello Comp. State: {state} </p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
function useStateCallback(initialState) {
const [state, setState] = useState(initialState);
const cbRef = useRef(null);
const setStateCallback = useCallback((state, cb) => {
cbRef.current = cb;
setState(state);
}, []);
useEffect(() => {
if (cbRef.current) {
cbRef.current(state);
cbRef.current = null;
}
}, [state]);
return [state, setStateCallback];
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>