当React组件的状态发生改变时,将调用render方法。因此,对于任何状态更改,都可以在渲染方法体中执行操作。那么,setState回调函数有特定的使用情况吗?
是的,有,因为 setState
以一种 异步
的方式工作。这意味着在调用 setState
后,this.state
变量不会立即更改。所以,如果您想在设置状态变量后立即执行操作并返回结果,则回调将非常有用。
考虑下面的例子:
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value });
this.validateTitle();
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
....
changeTitle: function changeTitle (event) {
this.setState({ title: event.target.value }, function() {
this.validateTitle();
});
},
validateTitle: function validateTitle () {
if (this.state.title.length === 0) {
this.setState({ titleError: "Title can't be blank" });
}
},
....
dispatch
一个动作时,你会想要在回调中执行而不是在render()
方法中执行,因为每次重新渲染时都会调用该方法,因此可能会出现许多这样的情况,你需要回调函数。
另一种情况是API调用
可能会出现这样一种情况,当特定状态发生更改时需要进行API调用,如果在渲染方法中执行,则会在每次呈现onState
更改或因某些传递给Child Component
的属性发生更改时被调用。setState回调
将更新后的状态值传递给API调用。....
changeTitle: function (event) {
this.setState({ title: event.target.value }, () => this.APICallFunction());
},
APICallFunction: function () {
// Call API with the updated value
}
....
this.state.title.length
,对吗? - Dmitry MinkovskysetState(state => state.title.length ? { titleError: "Title can't be blank" } : null)
,然后更改就会堆叠起来。不需要进行双重渲染。 - R Esmondthis.setState({
name:'value'
},() => {
console.log(this.state.name);
});
我脑海中首先想到的用例是api
调用,它不应该进入渲染,因为它会在每个状态改变时运行。 API调用应该仅在特定状态更改时执行,而不是在每次渲染时执行。
changeSearchParams = (params) => {
this.setState({ params }, this.performSearch)
}
performSearch = () => {
API.search(this.state.params, (result) => {
this.setState({ result })
});
}
因此,对于任何状态变化,都可以在渲染方法体中执行操作。
这是一种非常不好的做法,因为render
方法应该是纯净的,也就是说,不应该执行任何动作、状态变更或API调用,只需组合您的视图并返回它。操作应该仅在某些事件上执行。例如,componentDidMount
就是一个事件而不是渲染。
考虑setState调用
this.setState({ counter: this.state.counter + 1 })
IDEA
setState
可能在异步函数中被调用
因此,您不能依赖于this
。如果上述调用是在异步函数内部进行的,则this
将引用该组件状态的那一时刻,但我们希望这将引用在调用setState
或开始异步任务时状态内的属性。由于任务是异步调用,因此该属性可能已经发生了变化。因此,使用callback
函数来引用状态的某个属性是不可靠的,其参数为previousState
和props
,这意味着当异步任务完成并且该使用setState
调用更新状态时,prevState
将引用当前状态,即当setState
尚未启动时的状态,确保nextState
不会被损坏。
错误代码:会导致数据损坏
this.setState(
{counter:this.state.counter+1}
);
使用带有回调函数的setState来纠正代码:
this.setState(
(prevState,props)=>{
return {counter:prevState.counter+1};
}
);
async componentDidMount() {
const appConfig = getCustomerConfig();
this.setState({enableModal: appConfig?.enableFeatures?.paymentModal }, async
()=>{
if(this.state.enableModal){
//make some API call for data needed in poput
}
});
}
在渲染函数中,UI块需要 enableModal
布尔值,这就是为什么我在这里使用了 setState
,否则只需检查一次条件,然后调用API设置或不设置即可。
所有的答案都是关于如何将一个回调函数传递给类组件的setState函数。但对于一个函数组件,我强烈建议使用自定义的钩子函数:
import { useRef, useCallback, useEffect, useState } from 'react';
import type { Dispatch, SetStateAction } from 'react';
import isFunction from 'lodash.isfunction';
type StateFunctionType<S> = Dispatch<SetStateAction<S>>;
export type SetStateCallbackGeneric<S> = (
x: S | StateFunctionType<S>,
cb?: (newState: S) => void
) => void;
const useStateCallback = <T>(
initialState: T
): [T, SetStateCallbackGeneric<T>] => {
const [state, setState] = useState<T>(initialState);
const cbRef = useRef<any>(null);
const setStateCallback: SetStateCallbackGeneric<T> = useCallback(
(newState, cb) => {
cbRef.current = cb;
setState(newState as any);
},
[]
);
useEffect(() => {
if (isFunction(cbRef?.current)) {
// @ts-ignore
cbRef?.current?.(state);
cbRef.current = null;
}
}, [state]);
return [state, setStateCallback];
};
export default useStateCallback;
const [text, setText] = useStateCallback<string>('')
const handleFoo = (txt: string) => {
setText(txt, () => {
// Do What you want exactly AFTER text gets updated
})
};
setState
回调函数有一个罕见但重要的用例。
当您更新属性 x
后想要重新计算状态,并且您的重新计算可能会再次更新 x
。您不能仅仅依赖于 x
的 useEffect
,因为 React 会将其视为“检测到循环依赖”。您可以通过使用 setState
回调函数来规避 React。但请确保您不会创建无限循环。
我已经在两个大型 React 项目中应用了这个解决方案。
render()
中,则每次更新任何状态时它都会运行,这可能不是你想要的。这也会使你的代码变得难以阅读和理解。 - M3RS