如何在反应挂钩上使用 `setState` 回调
How to use `setState` callback on react hooks
React hooks 引入了 useState
来设置组件状态。但是我怎样才能像下面的代码那样使用钩子来替换回调:
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]);
如果你想更新以前的状态,那么你可以在钩子中这样做:
const [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
UseEffect 是主要的解决方案。但正如 Darryl 提到的,使用 useEffect 并将状态作为第二个参数传递有一个缺陷,组件将 运行 在初始化过程中。如果你只是想让回调函数 运行 使用更新状态的值,你可以设置一个本地常量并在 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);
}
你的问题很valid.Let我告诉你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 块,以便在满足条件时仅执行该代码块。然而,随着依赖项数组的变化,时间影响 运行s 但 IF 代码内部效果将仅在特定条件下执行。
用useEffect
、only firing on state updates模拟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.
setState()
将对组件状态的更改排入队列并告诉 React 该组件及其子组件需要 re-rendered 更新后的状态。
setState 方法是异步的,事实上,它没有 return 承诺。所以在我们想要更新或调用函数的情况下,可以在 setState 函数中将函数作为第二个参数调用回调。
例如,在你上面的例子中,你调用了一个函数作为 setState 回调。
setState(
{ name: "Michael" },
() => console.log(this.state)
);
上面的代码在class组件上可以正常工作,但是在函数组件的情况下,我们不能使用setState方法,这我们可以利用use effect hook来达到同样的效果。
想到的明显方法是 ypu 可以与 useEffect 一起使用,如下所示:
const [state, setState] = useState({ name: "Michael" })
useEffect(() => {
console.log(state) // do something after state has updated
}, [state])
但这也会在第一次渲染时触发,所以我们可以更改代码如下,我们可以检查第一次渲染事件并避免状态渲染。因此可以通过以下方式实现:
我们可以在这里使用用户挂钩来识别第一个渲染。
useRef Hook 允许我们在功能组件中创建可变变量。它对于访问 DOM nodes/React 元素和存储可变变量而不触发 re-render.
很有用
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];
};
我 运行 遇到了同样的问题,在我的设置中使用 useEffect 并没有解决问题(我正在从多个子组件的数组更新父状态,我需要知道更新了哪个组件数据)。
在承诺中包装 setState 允许在完成后触发任意操作:
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;
下面的问题让我找到了正确的方向:
这个怎么样:
const [Name, setName] = useState("");
...
onClick={()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}
你可以使用我知道的以下方式来获取更新后的最新状态:
const [state, setState] = useState({name: "Michael"});
const handleChangeName = () => {
setState({name: "Jack"});
}
useEffect(() => {
console.log(state.name); //"Jack"
//do something here
}, [state]);
- 功能更新
https://reactjs.org/docs/hooks-reference.html#functional-updates
“如果新状态是使用先前状态计算的,则可以将函数传递给 setState。该函数将接收先前值,以及 return 更新值。”
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;
});
}
- 使用引用
https://reactjs.org/docs/hooks-reference.html#useref
“returned ref 对象将在组件的整个生命周期内持续存在。”
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);
}
在react syntheticEvent handler中,setState是一个批量更新的过程,所以每次状态的改变都会被等待,return一个新的状态。
"setState() 并不总是立即更新组件。它可能会分批更新或推迟更新。",
https://reactjs.org/docs/react-component.html#setstate
这是一个有用的link
我们可以编写一个名为 useScheduleNextRenderCallback
的钩子,returns 一个“调度”函数。在我们调用 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>;
};
如果有人还需要的话,我用打字稿写了自定义钩子。
import 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];
}
在大家的帮助下,我实现了这个自定义挂钩:
非常类似于基于class的this.setState(状态、回调)
const useStateWithCallback = (initialState) => {
const [state, setState] = useState(initialState);
const callbackRef = useRef(() => undefined);
const setStateCB = (newState, callback) => {
callbackRef.current = callback;
setState(newState);
};
useEffect(() => {
callbackRef.current?.();
}, [state]);
return [state, setStateCB];
};
这样我们就可以像..
const [isVisible, setIsVisible] = useStateWithCallback(false);
...
setIsVisible(true, () => console.log('callback called now!! =)');
保持冷静和快乐的编码!
我觉得用useRef区分挂载与否不是一个好办法,通过判断useEffect()中useState()生成的值是否为初始值不是更好吗?
const [val, setVal] = useState(null)
useEffect(() => {
if (val === null) return
console.log('not mounted, val updated', val)
}, [val])
在我们拥有对 setState 回调的本地内置支持之前,我们可以使用简单的 javascript 方式...调用该函数并将新变量直接传递给它。
const [counter, setCounter] = useState(0);
const doSomething = () => {
const newCounter = 123
setCounter(newCounter);
doSomethingWCounter(newCounter);
};
function doSomethingWCounter(newCounter) {
console.log(newCounter); // 123
}
简单的解决方案,只需安装
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..."
/>
);
};
如果您不需要异步更新状态,您可以使用 ref 来保存值而不是 useState
。
const name = useRef("John");
name.current = "Michael";
console.log(name.current); // will print "Michael" since updating the ref is not async
我想你需要的是useState
和useCallback
:
示例代码
import React, { useCallback, useState } from 'react';
const Test = () => {
const [name, setName] = useState("");
const testCallback = useCallback(() => console.log(name), [name]);
return (
<button onClick={() => {
setName("Michael")
testCallback();
}}>Name</button>
)
};
export default Test;
我探索了 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)
}
已编辑
这里使用promise貌似还是推迟了rerender后的执行,触发两次setState
可能是获取最新状态的最佳方案。因为 setState 将被列出,我们只需要在重新渲染之前获取 prevState
即可使用。
原版Post
我只是想知道我们是否可以在这里使用 Promise
让 setState 变得可等待。
这是我的实验结果,感觉比使用回调更好
主要临时触发resolve函数在useEffect
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>
)
}
React hooks 引入了 useState
来设置组件状态。但是我怎样才能像下面的代码那样使用钩子来替换回调:
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]);
如果你想更新以前的状态,那么你可以在钩子中这样做:
const [count, setCount] = useState(0);
setCount(previousCount => previousCount + 1);
UseEffect 是主要的解决方案。但正如 Darryl 提到的,使用 useEffect 并将状态作为第二个参数传递有一个缺陷,组件将 运行 在初始化过程中。如果你只是想让回调函数 运行 使用更新状态的值,你可以设置一个本地常量并在 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);
}
你的问题很valid.Let我告诉你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 块,以便在满足条件时仅执行该代码块。然而,随着依赖项数组的变化,时间影响 运行s 但 IF 代码内部效果将仅在特定条件下执行。
用useEffect
、only firing on state updates模拟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.
setState()
将对组件状态的更改排入队列并告诉 React 该组件及其子组件需要 re-rendered 更新后的状态。
setState 方法是异步的,事实上,它没有 return 承诺。所以在我们想要更新或调用函数的情况下,可以在 setState 函数中将函数作为第二个参数调用回调。 例如,在你上面的例子中,你调用了一个函数作为 setState 回调。
setState(
{ name: "Michael" },
() => console.log(this.state)
);
上面的代码在class组件上可以正常工作,但是在函数组件的情况下,我们不能使用setState方法,这我们可以利用use effect hook来达到同样的效果。
想到的明显方法是 ypu 可以与 useEffect 一起使用,如下所示:
const [state, setState] = useState({ name: "Michael" })
useEffect(() => {
console.log(state) // do something after state has updated
}, [state])
但这也会在第一次渲染时触发,所以我们可以更改代码如下,我们可以检查第一次渲染事件并避免状态渲染。因此可以通过以下方式实现:
我们可以在这里使用用户挂钩来识别第一个渲染。
useRef Hook 允许我们在功能组件中创建可变变量。它对于访问 DOM nodes/React 元素和存储可变变量而不触发 re-render.
很有用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];
};
我 运行 遇到了同样的问题,在我的设置中使用 useEffect 并没有解决问题(我正在从多个子组件的数组更新父状态,我需要知道更新了哪个组件数据)。
在承诺中包装 setState 允许在完成后触发任意操作:
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;
下面的问题让我找到了正确的方向:
这个怎么样:
const [Name, setName] = useState("");
...
onClick={()=>{
setName("Michael")
setName(prevName=>{...}) //prevName is Michael?
}}
你可以使用我知道的以下方式来获取更新后的最新状态:
const [state, setState] = useState({name: "Michael"});
const handleChangeName = () => {
setState({name: "Jack"});
}
useEffect(() => {
console.log(state.name); //"Jack"
//do something here
}, [state]);
- 功能更新
https://reactjs.org/docs/hooks-reference.html#functional-updates
“如果新状态是使用先前状态计算的,则可以将函数传递给 setState。该函数将接收先前值,以及 return 更新值。”
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;
});
}
- 使用引用
https://reactjs.org/docs/hooks-reference.html#useref
“returned ref 对象将在组件的整个生命周期内持续存在。”
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);
}
在react syntheticEvent handler中,setState是一个批量更新的过程,所以每次状态的改变都会被等待,return一个新的状态。
"setState() 并不总是立即更新组件。它可能会分批更新或推迟更新。",
https://reactjs.org/docs/react-component.html#setstate
这是一个有用的link
我们可以编写一个名为 useScheduleNextRenderCallback
的钩子,returns 一个“调度”函数。在我们调用 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>;
};
如果有人还需要的话,我用打字稿写了自定义钩子。
import 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];
}
在大家的帮助下,我实现了这个自定义挂钩:
非常类似于基于class的this.setState(状态、回调)
const useStateWithCallback = (initialState) => {
const [state, setState] = useState(initialState);
const callbackRef = useRef(() => undefined);
const setStateCB = (newState, callback) => {
callbackRef.current = callback;
setState(newState);
};
useEffect(() => {
callbackRef.current?.();
}, [state]);
return [state, setStateCB];
};
这样我们就可以像..
const [isVisible, setIsVisible] = useStateWithCallback(false);
...
setIsVisible(true, () => console.log('callback called now!! =)');
保持冷静和快乐的编码!
我觉得用useRef区分挂载与否不是一个好办法,通过判断useEffect()中useState()生成的值是否为初始值不是更好吗?
const [val, setVal] = useState(null)
useEffect(() => {
if (val === null) return
console.log('not mounted, val updated', val)
}, [val])
在我们拥有对 setState 回调的本地内置支持之前,我们可以使用简单的 javascript 方式...调用该函数并将新变量直接传递给它。
const [counter, setCounter] = useState(0);
const doSomething = () => {
const newCounter = 123
setCounter(newCounter);
doSomethingWCounter(newCounter);
};
function doSomethingWCounter(newCounter) {
console.log(newCounter); // 123
}
简单的解决方案,只需安装
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..."
/>
);
};
如果您不需要异步更新状态,您可以使用 ref 来保存值而不是 useState
。
const name = useRef("John");
name.current = "Michael";
console.log(name.current); // will print "Michael" since updating the ref is not async
我想你需要的是useState
和useCallback
:
示例代码
import React, { useCallback, useState } from 'react';
const Test = () => {
const [name, setName] = useState("");
const testCallback = useCallback(() => console.log(name), [name]);
return (
<button onClick={() => {
setName("Michael")
testCallback();
}}>Name</button>
)
};
export default Test;
我探索了 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)
}
已编辑
这里使用promise貌似还是推迟了rerender后的执行,触发两次setState
可能是获取最新状态的最佳方案。因为 setState 将被列出,我们只需要在重新渲染之前获取 prevState
即可使用。
原版Post
我只是想知道我们是否可以在这里使用 Promise
让 setState 变得可等待。
这是我的实验结果,感觉比使用回调更好
主要临时触发resolve函数在useEffect
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>
)
}