使用 react-hooks 更新状态时执行异步代码
Posted
技术标签:
【中文标题】使用 react-hooks 更新状态时执行异步代码【英文标题】:Executing async code on update of state with react-hooks 【发布时间】:2019-05-22 18:05:39 【问题描述】:我有类似的东西:
const [loading, setLoading] = useState(false);
...
setLoading(true);
doSomething(); // <--- when here, loading is still false.
设置状态仍然是异步的,那么等待setLoading()
调用完成的最佳方法是什么?
setLoading()
似乎不像以前的 setState()
那样接受回调。
一个例子
基于类
getNextPage = () =>
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (this.state.pagesSeen.includes(this.state.page + 1))
return this.setState(
page: this.state.page + 1,
);
if (this.state.prefetchedOrders)
const allOrders = this.state.orders.concat(this.state.prefetchedOrders);
return this.setState(
orders: allOrders,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
prefetchedOrders: null,
);
this.setState(
isLoading: true,
,
() =>
getOrders(
page: this.state.page + 1,
query: this.state.query,
held: this.state.holdMode,
statuses: filterMap[this.state.filterBy],
)
.then((o) =>
const orders = o.data;
const allOrders = this.state.orders.concat(orders);
this.setState(
orders: allOrders,
isLoading: false,
page: this.state.page + 1,
pagesSeen: [...this.state.pagesSeen, this.state.page + 1],
// Just in case we're in the middle of a prefetch.
prefetchedOrders: null,
);
)
.catch(e => console.error(e.message));
,
);
;
转换为基于函数的
const getNextPage = () =>
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1))
return setPage(page + 1);
if (prefetchedOrders)
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
setIsLoading(true);
getOrders(
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
)
.then((o) =>
const orders: fetchedOrders = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
)
.catch(e => console.error(e.message));
;
在上面,我们希望按顺序运行每个 setWhatever 调用。这是否意味着我们需要设置许多不同的 useEffect 挂钩来复制这种行为?
【问题讨论】:
【参考方案1】:useState
setter 不会像在 React 类组件中的 setState 那样在状态更新完成后提供回调。为了复制相同的行为,您可以在 React 类组件中使用类似的模式,如 componentDidUpdate
生命周期方法和 useEffect
using Hooks
useEffect
hooks 将第二个参数作为一个值数组,React 需要在渲染周期完成后监控这些值的变化。
const [loading, setLoading] = useState(false);
...
useEffect(() =>
doSomething(); // This is be executed when `loading` state changes
, [loading])
setLoading(true);
编辑
与setState
不同,useState
挂钩的更新程序没有回调,但您始终可以使用useEffect
来复制上述行为。但是你需要确定加载变化
代码的函数式方法如下所示
function usePrevious(value)
const ref = useRef();
useEffect(() =>
ref.current = value;
);
return ref.current;
const prevLoading = usePrevious(isLoading);
useEffect(() =>
if (!prevLoading && isLoading)
getOrders(
page: page + 1,
query: localQuery,
held: localHoldMode,
statuses: filterMap[filterBy],
)
.then((o) =>
const orders: fetchedOrders = o.data;
const allOrders = orders.concat(fetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
setIsLoading(false);
)
.catch(e => console.error(e.message));
, [isLoading, preFetchedOrders, orders, page, pagesSeen]);
const getNextPage = () =>
// This will scroll back to the top, and also trigger the prefetch for the next page on the way up.
goToTop();
if (pagesSeen.includes(page + 1))
return setPage(page + 1);
if (prefetchedOrders)
const allOrders = orders.concat(prefetchedOrders);
setOrders(allOrders);
setPage(page + 1);
setPagesSeen([...pagesSeen, page + 1]);
setPrefetchedOrders(null);
return;
setIsLoading(true);
;
【讨论】:
那么,在我们想要等待多个 setState 的情况下,我们需要多个 useEffect 钩子,对吧? @Colin,如果您想在不同的状态发生变化时采取不同的行动,那么可以,但是如果您想对这些多个状态中的任何一个进行更改,那么您可以传递多个参数,例如 @ 987654331@ 是的,所以在上述情况下,您将拥有[isLoading, orders]
但是要采取不同的行动,我们需要多个 useEffect 挂钩,对吧?另外,我们如何以这种方式执行顺序操作?更改不会同时触发两个钩子吗?
顺序操作是什么意思。如果您使用所需的行为更新您的代码,如果我知道的话,我可以看看并提出解决方案【参考方案2】:
等到您的组件重新渲染。
const [loading, setLoading] = useState(false);
useEffect(() =>
if (loading)
doSomething();
, [loading]);
setLoading(true);
您可以通过以下方式提高清晰度:
function doSomething()
// your side effects
// return () =>
function useEffectIf(condition, fn)
useEffect(() => condition && fn(), [condition])
function App()
const [loading, setLoading] = useState(false);
useEffectIf(loading, doSomething)
return (
<>
<div>loading</div>
<button onClick=() => setLoading(true)>Click Me</button>
</>
);
【讨论】:
嗯,所以我需要添加一个useEffect()
来监控每个状态以对其做出反应?
@Colin 您可以将 useEffect 视为 componentDidMount 或 componentDidUpdate 取决于上例中传递给回调的最后一个参数 [loading]
说实话,我不认为这是一个好的答案;你能更好地解释一下doSomething
的真正作用吗?你的用例是什么?
我认为现在这很有意义。这是一个人为的例子,但doSomething()
从 API 获取,如果有意义的话,需要知道 实际 当前状态。
@Colin 很好的解释,带有 hooks API 的例子egghead.io/lessons/react-use-the-usestate-react-hook【参考方案3】:
创建了一个自定义的useState
钩子,它的工作原理类似于普通的useState
钩子,只是这个自定义钩子的状态更新器函数需要一个回调,该回调将在状态更新和组件重新渲染后执行。
Typescript 解决方案
import useEffect, useRef, useState from 'react';
type OnUpdateCallback<T> = (s: T) => void;
type SetStateUpdaterCallback<T> = (s: T) => T;
type SetStateAction<T> = (newState: T | SetStateUpdaterCallback<T>, callback?: OnUpdateCallback<T>) => void;
export function useCustomState<T>(init: T): [T, SetStateAction<T>];
export function useCustomState<T = undefined>(init?: T): [T | undefined, SetStateAction<T | undefined>];
export function useCustomState<T>(init: T): [T, SetStateAction<T>]
const [state, setState] = useState<T>(init);
const cbRef = useRef<OnUpdateCallback<T>>();
const setCustomState: SetStateAction<T> = (newState, callback?): void =>
cbRef.current = callback;
setState(newState);
;
useEffect(() =>
if (cbRef.current)
cbRef.current(state);
cbRef.current = undefined;
, [state]);
return [state, setCustomState];
Javascript 解决方案
import useEffect, useRef, useState from 'react';
export function useCustomState(init)
const [state, setState] = useState(init);
const cbRef = useRef();
const setCustomState = (newState, callback) =>
cbRef.current = callback;
setState(newState);
;
useEffect(() =>
if (cbRef.current)
cbRef.current(state);
cbRef.current = undefined;
, [state]);
return [state, setCustomState];
用法
const [state, setState] = useCustomState(myInitialValue);
...
setState(myNewValueOrStateUpdaterCallback, () =>
// Function called after state update and component rerender
)
【讨论】:
【参考方案4】:我对此有一个建议。
您可以使用 React Ref 来存储状态变量的状态。然后使用 react ref 更新状态变量。这将渲染页面刷新,然后在异步函数中使用 React Ref。
const stateRef = React.useRef().current
const [state,setState] = useState(stateRef);
async function some()
stateRef = some: 'value'
setState(stateRef) // Triggers re-render
await some2();
async function some2()
await someHTTPFunctionCall(stateRef.some)
stateRef = null;
setState(stateRef) // Triggers re-render
【讨论】:
以上是关于使用 react-hooks 更新状态时执行异步代码的主要内容,如果未能解决你的问题,请参考以下文章
在 React-Hooks UseRef 我无法获得新的状态值