使用 response-hook 在状态更新时执行异步代码

我有这样的东西:

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));
};

在上面的代码中,我们希望按顺序运行每个 setAnycall。这是否意味着我们需要设置许多不同的 useEffect 挂钩来复制这种行为?

180299 次浏览

等待组件重新呈现。

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>
</>
);
}

在完成状态更新之后,useState setter 不像 React 类组件中的 setState 那样提供回调。为了复制相同的行为,可以使用类似的模式,比如使用 Hooks 对 useEffect的 React 类组件使用 componentDidUpdate生命周期方法

useEffect钩子将第二个参数作为一个值数组,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);
};

我有个建议。

您可以使用 React Ref 来存储状态变量的状态。然后使用 response 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
}


创建了一个定制的 useState钩子,其工作原理类似于普通的 useState钩子,只不过这个定制钩子的状态更新器函数接受一个回调函数,该回调函数将在状态更新和组件重新呈现之后执行。

打印解决方案

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
})

传递一个函数给 setter 而不是 value!

不直接给 setter 一个新值,而是给它一个箭头函数,该函数接受当前状态值并返回新值。

它将强制它链接状态更新,在完成所有更新之后,它将重新呈现组件。

const [counter, setCounter] = useState(0);


const incrementCount = () => {
setCounter( (counter) => { return counter + 1 } )
}

现在,每次调用 IncrementCount 时,计数都会增加1,不再停留在1。

您可以创建一个异步状态挂钩

const useAsyncState = initialState => {
const [state, setState] = useState(initialState);


const asyncSetState = value => {
return new Promise(resolve => {
setState(value);
setState((current) => {
resolve(current);
return current;
});
});
};


return [state, asyncSetState];
};

那么

const [loading, setLoading] = useAsyncState(false)


const submit = async () => {
await setLoading(true)
dosomething()
}