[英]Asynchronous calls with React.useMemo
場景相對簡單:我們有一個在遠程服務器上發生的長時間運行的按需計算。 我們想記住結果。 即使我們從遠程資源異步獲取,這也不是副作用,因為我們只想將計算結果顯示給用戶,我們絕對不想在每次渲染時都這樣做。
問題:似乎 React.useMemo 不直接支持 Typescript 的 async/await,會返回一個 promise:
//returns a promise:
let myMemoizedResult = React.useMemo(() => myLongAsyncFunction(args), [args])
//also returns a promise:
let myMemoizedResult = React.useMemo(() => (async () => await myLongAsyncFunction(args)), [args])
等待異步 function 的結果並使用 React.useMemo 記住結果的正確方法是什么? 我對純 JS 使用了常規承諾,但在這些類型的情況下仍然難以處理它們。
我嘗試過其他方法,例如 memoize-one,但問題似乎是由於 React function 組件的工作方式破壞了memoization, this
上下文發生了變化,這就是我嘗試使用 React.useMemo 的原因。
也許我正在嘗試在此處的圓孔中安裝一個方形釘子 - 如果是這樣的話,也很高興知道這一點。 現在我可能只是想推出自己的記憶 function。
編輯:我認為部分原因是我在使用 memoize-one 時犯了一個不同的愚蠢錯誤,但我仍然有興趣知道 React.memo 的答案。
這是一個片段 - 這個想法不是直接在渲染方法中使用記憶的結果,而是作為以事件驅動的方式引用的東西,即點擊計算按鈕時。
export const MyComponent: React.FC = () => {
let [arg, setArg] = React.useState('100');
let [result, setResult] = React.useState('Not yet calculated');
//My hang up at the moment is that myExpensiveResultObject is
//Promise<T> rather than T
let myExpensiveResultObject = React.useMemo(
async () => await SomeLongRunningApi(arg),
[arg]
);
const getResult = () => {
setResult(myExpensiveResultObject.interestingProperty);
}
return (
<div>
<p>Get your result:</p>
<input value={arg} onChange={e => setArg(e.target.value)}></input>
<button onClick={getResult}>Calculate</button>
<p>{`Result is ${result}`}</p>
</div>);
}
你真正想要的是在異步調用結束后重新渲染你的組件。 僅靠記憶並不能幫助您實現這一目標。 相反,您應該使用 React 的 state - 它會保留您的異步調用返回的值,並允許您觸發重新渲染。
此外,觸發異步調用是一種副作用,因此不應在渲染階段執行 - 既不在組件 function 的主體內,也不在渲染階段發生的useMemo(...)
內。 相反,所有副作用都應該在useEffect
中觸發。
這是完整的解決方案:
const [result, setResult] = useState()
useEffect(() => {
let active = true
load()
return () => { active = false }
async function load() {
setResult(undefined) // this is optional
const res = await someLongRunningApi(arg1, arg2)
if (!active) { return }
setResult(res)
}
}, [arg1, arg2])
這里我們在 useEffect 中調用 async useEffect
。 請注意,您不能在useEffect
異步中進行整個回調 - 這就是為什么我們在內部聲明一個異步 function load
並在不等待的情況下調用它。
一旦其中一個arg
更改,效果將重新運行 - 這在大多數情況下是您想要的。 因此,如果您在渲染時重新計算它們,請確保記住arg
s。 執行setResult(undefined)
是可選的——您可能希望在屏幕上保留上一個結果,直到獲得下一個結果。 或者您可以執行類似setLoading(true)
的操作,以便用戶知道發生了什么。
使用active
標志很重要。 沒有它,您將面臨等待發生的競爭條件:第二個異步 function 調用可能在第一個異步調用完成之前完成:
setResult()
發生setResult()
再次發生,用陳舊的結果覆蓋正確的結果並且您的組件以不一致的 state 結束。我們通過使用useEffect
的清理 function 來重置active
標志來避免這種情況:
active#1 = true
,開始第一次通話active#1 = false
active#2 = true
,開始第二次通話setResult()
發生setResult()
不會發生,因為active#1
為false
場景相對簡單:我們在遠程服務器上進行了長時間運行的按需計算。 我們想記住結果。 即使我們從遠程資源中異步獲取,這也不是副作用,因為我們只想將此計算的結果顯示給用戶,並且我們絕對不希望在每次渲染時都這樣做。
問題:React.useMemo 好像不直接支持Typescript的async/await,會返回一個promise:
//returns a promise:
let myMemoizedResult = React.useMemo(() => myLongAsyncFunction(args), [args])
//also returns a promise:
let myMemoizedResult = React.useMemo(() => (async () => await myLongAsyncFunction(args)), [args])
等待異步 function 的結果並使用 React.useMemo 記憶結果的正確方法是什么? 我在普通的 JS 中使用了常規的 Promise,但在這些類型的情況下仍然難以解決。
我嘗試了其他方法,例如 memoize-one,但問題似乎是this
React function 組件的工作方式破壞了memoization ,這就是我嘗試使用 React.useMemo 的原因。
也許我正試圖在此處的圓孔中安裝一個方形釘 - 如果是這種情況,也很高興知道這一點。 現在我可能只是要推出我自己的記憶 function。
編輯:我認為部分原因是我在使用 memoize-one 時犯了一個不同的愚蠢錯誤,但我仍然很想知道這里的答案 wrt.memo。
這是一個片段 - 這個想法不是直接在渲染方法中使用記憶結果,而是作為以事件驅動的方式引用的東西,即在計算按鈕單擊時。
export const MyComponent: React.FC = () => {
let [arg, setArg] = React.useState('100');
let [result, setResult] = React.useState('Not yet calculated');
//My hang up at the moment is that myExpensiveResultObject is
//Promise<T> rather than T
let myExpensiveResultObject = React.useMemo(
async () => await SomeLongRunningApi(arg),
[arg]
);
const getResult = () => {
setResult(myExpensiveResultObject.interestingProperty);
}
return (
<div>
<p>Get your result:</p>
<input value={arg} onChange={e => setArg(e.target.value)}></input>
<button onClick={getResult}>Calculate</button>
<p>{`Result is ${result}`}</p>
</div>);
}
編輯:由於調用的異步性質,我在下面的原始答案似乎有一些意想不到的副作用。 相反,我會嘗試考慮在服務器上記住實際計算,或者使用自己編寫的閉包來檢查arg
是否沒有改變。 否則你仍然可以使用像我下面描述的useEffect
這樣的東西。
我認為問題在於async
函數總是隱式返回 promise。既然是這種情況,您可以直接await
結果來解包 promise:
const getResult = async () => {
const result = await myExpensiveResultObject;
setResult(result.interestingProperty);
};
我確實認為,雖然更好的模式可能是利用useEffect
,它依賴於某些 state object ,在這種情況下僅在單擊按鈕時設置,但似乎useMemo
也應該工作。
我認為 React 特別提到不應該使用useMemo來管理像異步 API 調用這樣的副作用。 它們應該在useEffect
掛鈎中進行管理,其中設置了適當的依賴項以確定它們是否應該重新運行。
首先,90% 的功勞歸於 ---> torvin 。
我的項目需要使用 UseMemo 設置一些數據,是或是,useState 無法正常工作。 因此,使用 @torvin 的相同實現,我將 useMemo 設置為其返回值。
const [status, setstatus] = useState();
const _statusMemo = useMemo(() => status, [status]); /// Here We set up the UseMemo
useEffect(() => {
if (_statusMemo !== undefined) return;
let active = true;
load()
return () => { active = false }
async function load() {
setstatus(undefined) // this is optional
const res = await Promise.resolve(promise_function);
if (!active) { return }
setstatus(res);
}
}, [])
根據useMemo DOCS :
傳遞一個“創建” function 和一個依賴數組。 useMemo 只會在依賴項之一發生更改時重新計算記憶值。 這種優化有助於避免在每次渲染時進行昂貴的計算。
所以這一行:
const _statusMemo = useMemo(() => status, [status]);
基本上充當監聽器,一旦異步 useEffect function 為status
分配一個值,useMemo 就會被觸發並將其值設置為它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.