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
钩子一起使用。
你需要使用useEffect
钩子来实现这个功能。
const [counter, setCounter] = useState(0);
const doSomething = () => {
setCounter(123);
}
useEffect(() => {
console.log('Do something after counter has changed', counter);
}, [counter]);
useEffect
回调,则需要相应地修改代码。import React, { useEffect, useRef } from 'react';
const [counter, setCounter] = useState(0);
const didMount = useRef(false);
const doSomething = () => {
setCounter(123);
}
useEffect(() => {
// Return early, if this is the first render:
if ( !didMount.current ) {
return didMount.current = true;
}
// Paste code to be executed on subsequent renders:
console.log('Do something after counter has changed', counter);
}, [counter]);
console.log
,以及每当 counter
改变时。如果你只想在状态更新后执行某些操作而不是在初始渲染时执行,因为初始值已经设置好了,该怎么办呢?我猜你可以在 useEffect
中检查值,并决定是否要执行该操作。这样做算不算最佳实践? - Darryl YoungWarning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
- Greg SadetskyuseEffect
不适用于所有场景。有时状态会从多个地方更新,但您希望只从其中一个地方调用。如何轻松区分?在这种情况下,回调函数是完美的选择。一种hack方法是使用另一个丑陋的 useState
,仅用于检测特定更改。非常丑陋... - vsyncuseState
钩子中。它让人感觉像是从类组件的this.setState
退回了一步。:/ - Kongconst [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
文档:
预期为赋值或函数调用,但看到了一个表达式
。 - tonitone120使用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);
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。
state
是指回调函数被调用时的先前状态。是这样吗? - MJ Studioconst [state, setState] = useStateCallback(1);setState(2, (n) => {console.log(n, state) });
为什么上述代码中,n是2但state仍然是1,是否有任何方法可以使state也打印出2? - Jochen Shi我曾遇到同样的问题,使用 useEffect 在我的设置中没有起作用(我从多个子组件的数组更新父组件的状态,并且需要知道哪个组件更新了数据)。
将 setState 包装在一个 Promise 中可以在完成后触发任意操作:
import React, {useState} from 'react'
function App() {
const [count, setCount] = useState(0)
function handleClick(){
Promise.resolve()
.then(() => { setCount(count => count+1)})
.then(() => console.log(count))
}
return (
<button onClick= {handleClick}> Increase counter </button>
)
}
export default App;
以下问题引导我朝着正确的方向: 使用hooks时,React是否批处理状态更新函数?
setCount
是异步的,对吗?如果是这样,就有可能出现竞态条件,console.log
可能会打印出旧值。 - RavenManimport React, { useEffect, useRef, useState } from "react";
export const useStateWithCallback = <T>(initialState: T): [state: T, setState: (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => void] => {
const [state, setState] = useState<T>(initialState);
const callbackRef = useRef<(updated: T) => void>();
const handleSetState = (updatedState: React.SetStateAction<T>, callback?: (updatedState: T) => void) => {
callbackRef.current = callback;
setState(updatedState);
};
useEffect(() => {
if (typeof callbackRef.current === "function") {
callbackRef.current(state);
callbackRef.current = undefined;
}
}, [state]);
return [state, handleSetState];
}
在更新后获取最新状态的方法如下:
const [state, setState] = useState({name: "Michael"});
const handleChangeName = () => {
setState({name: "Jack"});
}
useEffect(() => {
console.log(state.name); //"Jack"
//do something here
}, [state]);
const [state, setState] = useState({name: "Michael"});
const handleChangeName = () => {
setState({name: "Jack"})
setState(prevState => {
console.log(prevState.name);//"Jack"
//do something here
// return updated state
return prevState;
});
}
const [state, setState] = useState({name: "Michael"});
const stateRef = useRef(state);
stateRef.current = state;
const handleClick = () => {
setState({name: "Jack"});
setTimeout(() => {
//it refers to old state object
console.log(state.name);// "Michael";
//out of syntheticEvent and after batch update
console.log(stateRef.current.name);//"Jack"
//do something here
}, 0);
}
setState()
方法将组件状态的更改排队,并告诉 React,需要使用更新后的状态重新渲染此组件及其子组件。
setState
方法是异步进行的,事实上它并不返回Promise。因此,在我们想要更新或调用函数的情况下,可以将该函数作为setState
函数的第二个参数传入callback。
例如,在上面的例子中,您已经将一个函数作为setState
回调调用了。
setState(
{ name: "Michael" },
() => console.log(this.state)
);
上面的代码对于类组件运行良好,但是在函数式组件中,我们不能使用setState方法。因此,我们可以利用useEffect hook来实现相同的结果。
显而易见的方法是,您可以使用以下useEffect:
const [state, setState] = useState({ name: "Michael" })
useEffect(() => {
console.log(state) // do something after state has updated
}, [state])
但这会在第一次渲染时触发,因此我们可以将代码更改如下,在其中检查第一次渲染事件并避免状态渲染。 因此,可以按以下方式执行实现:
在此处我们可以使用用户钩子来识别第一次渲染。
useRef Hook允许我们在功能组件中创建可变变量。它对于访问DOM节点/React元素以及存储可变变量而不触发重新渲染非常有用。
const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);
useEffect(() => {
if (!firstTimeRender.current) {
console.log(state);
}
}, [state])
useEffect(() => {
firstTimeRender.current = false
}, [])
我有一个使用场景,我想在设置状态后进行一些带参数的 API 调用。我不想将这些参数设置为我的状态,所以我创建了一个自定义钩子,以下是我的解决方案:
import { useState, useCallback, useRef, useEffect } from 'react';
import _isFunction from 'lodash/isFunction';
import _noop from 'lodash/noop';
export const useStateWithCallback = initialState => {
const [state, setState] = useState(initialState);
const callbackRef = useRef(_noop);
const handleStateChange = useCallback((updatedState, callback) => {
setState(updatedState);
if (_isFunction(callback)) callbackRef.current = callback;
}, []);
useEffect(() => {
callbackRef.current();
callbackRef.current = _noop; // to clear the callback after it is executed
}, [state]);
return [state, handleStateChange];
};
useScheduleNextRenderCallback
的钩子函数,它返回一个"schedule"函数。在调用setState
后,我们可以调用"schedule"函数,传递我们想要在下一次渲染时运行的回调函数。import { useCallback, useEffect, useRef } from "react";
type ScheduledCallback = () => void;
export const useScheduleNextRenderCallback = () => {
const ref = useRef<ScheduledCallback>();
useEffect(() => {
if (ref.current !== undefined) {
ref.current();
ref.current = undefined;
}
});
const schedule = useCallback((fn: ScheduledCallback) => {
ref.current = fn;
}, []);
return schedule;
};
使用示例:
const App = () => {
const scheduleNextRenderCallback = useScheduleNextRenderCallback();
const [state, setState] = useState(0);
const onClick = useCallback(() => {
setState(state => state + 1);
scheduleNextRenderCallback(() => {
console.log("next render");
});
}, []);
return <button onClick={onClick}>click me to update state</button>;
};