簡體   English   中英

使用 React.useMemo 進行異步調用

[英]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 調用可能在第一個異步調用完成之前完成:

  1. 開始第一次通話
  2. 開始第二個通話
  3. 第二次調用結束, setResult()發生
  4. 第一次調用結束, setResult()再次發生,用陳舊的結果覆蓋正確的結果

並且您的組件以不一致的 state 結束。我們通過使用useEffect的清理 function 來重置active標志來避免這種情況:

  1. 設置active#1 = true ,開始第一次通話
  2. arg 更改,調用清理 function,設置active#1 = false
  3. 設置active#2 = true ,開始第二次通話
  4. 第二次調用結束, setResult()發生
  5. 第一次調用完成, setResult()不會發生,因為active#1false

場景相對簡單:我們在遠程服務器上進行了長時間運行的按需計算。 我們想記住結果。 即使我們從遠程資源中異步獲取,這也不是副作用,因為我們只想將此計算的結果顯示給用戶,並且我們絕對不希望在每次渲染時都這樣做。

問題: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掛鈎中進行管理,其中設置了適當的依賴項以確定它們是否應該重新運行。

從 Async / Promise function Hack 添加數據到 useMemo

首先,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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM