React钩子引入了useState来设置组件状态。但是我如何使用钩子来替换下面的回调代码:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
我想在状态更新后做一些事情。
我知道我可以使用useEffect来做额外的事情,但我必须检查之前的状态值,这需要位代码。我正在寻找一个简单的解决方案,可以使用useState挂钩。
React钩子引入了useState来设置组件状态。但是我如何使用钩子来替换下面的回调代码:
setState(
{ name: "Michael" },
() => console.log(this.state)
);
我想在状态更新后做一些事情。
我知道我可以使用useEffect来做额外的事情,但我必须检查之前的状态值,这需要位代码。我正在寻找一个简单的解决方案,可以使用useState挂钩。
当前回答
传递一个函数怎么样?
const [name, setName] = useState(initialName);
...
setName(() => {
const nextName = "Michael";
console.log(nextName);
return nextName;
});
其他回答
我不认为用useRef区分挂载与否是一个好方法,不是一个更好的方法来确定useState()在useEffect()中生成的值是否为初始值?
const [val, setVal] = useState(null)
useEffect(() => {
if (val === null) return
console.log('not mounted, val updated', val)
}, [val])
我们可以写一个叫做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>;
};
简化的测试用例:https://stackblitz.com/edit/react-ts-rjd9jk
传递一个函数怎么样?
const [name, setName] = useState(initialName);
...
setName(() => {
const nextName = "Michael";
console.log(nextName);
return nextName;
});
编辑
在这里使用promise似乎仍然推迟了重新渲染后的执行,两次触发setState可能是获得最新状态的最佳解决方案。因为setState将被列出,我们只需要在重新渲染之前获得prevState。
最初的发布
我刚刚弄清楚了我们是否可以在这里使用一个Promise让setState变成可等待的。
这是我的实验结果,感觉比使用回调更好
主要是在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>
)
}
setState()将更改排队到组件状态,并告诉React该组件及其子组件需要使用更新后的状态重新呈现。
setState方法是异步的,实际上,它并不返回承诺。在我们想要更新或调用一个函数的情况下,函数可以在setState函数中调用callback作为第二个参数。 例如,在上面的例子中,您调用了一个函数作为setState回调函数。
setState(
{ name: "Michael" },
() => console.log(this.state)
);
上面的代码适用于类组件,但对于函数组件,我们不能使用setState方法,因此我们可以利用use effect钩子来实现相同的结果。
显而易见的方法是,你可以使用useEffect,如下所示:
const [state, setState] = useState({ name: "Michael" })
useEffect(() => {
console.log(state) // do something after state has updated
}, [state])
但这也会在第一次呈现时触发,因此我们可以更改如下代码,检查第一次呈现事件并避免状态呈现。因此,可以通过以下方式实现:
我们可以在这里使用user钩子来标识第一次渲染。
useRef钩子允许我们在函数组件中创建可变变量。它对于访问DOM节点/React元素和存储可变变量而不触发重新渲染非常有用。
const [state, setState] = useState({ name: "Michael" });
const firstTimeRender = useRef(true);
useEffect(() => {
if (!firstTimeRender.current) {
console.log(state);
}
}, [state])
useEffect(() => {
firstTimeRender.current = false
}, [])