Асинхронные вызовы с React.useMemo
Сценарий относительно прост: у нас есть длительные вычисления по запросу, которые происходят на удаленном сервере. Мы хотим запомнить результат. Несмотря на то, что мы выполняем асинхронную выборку из удаленного ресурса, это не побочный эффект, потому что мы просто хотим, чтобы результат этого вычисления отображался для пользователя, и мы определенно не хотим делать это при каждой визуализации.
Проблема: похоже, что React.useMemo напрямую не поддерживает async/await в Typescript и вернет обещание:
//returns a promise:
let myMemoizedResult = React.useMemo(() => myLongAsyncFunction(args), [args])
//also returns a promise:
let myMemoizedResult = React.useMemo(() => (async () => await myLongAsyncFunction(args)), [args])
Как правильно ожидать результата от асинхронной функции и запоминать результат с помощью React.useMemo? Я использовал обычные обещания с простым JS, но все еще борюсь с ними в подобных ситуациях.
Я пробовал другие подходы, такие как memoize-one, но проблема, похоже, в том, что this
изменения контекста из-за того, как работают компоненты функции React, нарушают мемоизацию, поэтому я пытаюсь использовать React.useMemo.
Может быть, я пытаюсь вставить квадратный колышек в круглое отверстие - в таком случае было бы неплохо знать и об этом. А пока я, наверное, просто открою свою собственную функцию запоминания.
Изменить: я думаю, что отчасти это было связано с тем, что я совершал другую глупую ошибку с 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>);
}
5 ответов
Изменить: мой исходный ответ ниже, похоже, имеет некоторые непредвиденные побочные эффекты из-за асинхронного характера вызова. Вместо этого я бы попытался либо подумать о запоминании фактических вычислений на сервере, либо использовать самописное закрытие, чтобы проверить,arg
не изменилось. В противном случае вы все равно можете использовать что-то вродеuseEffect
как я описал ниже.
Я считаю, что проблема в том, что async
функции всегда неявно возвращают обещание. Поскольку это так, вы можете напрямуюawait
результат, чтобы развернуть обещание:
const getResult = async () => {
const result = await myExpensiveResultObject;
setResult(result.interestingProperty);
};
См. Пример кода и коробки здесь.
Я действительно думаю, что лучшим вариантом может быть использование useEffect
который имеет зависимость от некоторого объекта состояния, который в этом случае устанавливается только при нажатии кнопки, но похоже, что useMemo
тоже должно работать.
Код в ответе @teve вводит условие гонки. Во избежание этого следует использовать функцию очистки. Вот полное решение:
const [result, setResult] = useState();
useEffect(() => {
let active = true
load()
return () => { active = false }
async function load() {
setResult(undefined) // this is optional
const res = await someLongRunningApi(arg)
if (!mounted) { return }
setResult(res)
}
}, [arg])
Здесь когда-то значение
arg
изменения,
active
будет установлено значение false внутри предыдущего
useEffect
закрытие, предотвращающее возникновение состояния гонки.
Проблема с кодом @ steve
Код в ответе @teve вводит состояние гонки: если асинхронный вызов запускается дважды, первое обещание может занять больше времени для разрешения и перезапишет значение, возвращаемое вторым обещанием.
Представьте, что у нас есть гипотетическая асинхронная функция вроде этой:
async function doWork(value: number) {
return new Promise<number>(resolve => {
setTimeout(Math.random() * 1000, () => resolve(value))
})
}
Он возвращает переданное число после случайной задержки. Теперь представьте, что мы вызываем его дважды. Вот что могло случиться:
- первый
doWork(1)
звонок инициирован - второй
doWork(2)
звонок инициирован - запускается на обещании, возвращенном из второго вызова, вызывая
setResult(2)
-
.then()
запускается на обещании, возвращенном из первого вызова, вызываяsetResult(1)
и ваш компонент оказывается в несогласованном состоянии.
Я думаю, что React специально упоминает, что useMemo не следует использовать для управления побочными эффектами, такими как асинхронные вызовы API. Им следует управлять в
useEffect
хуки, для которых установлены правильные зависимости, чтобы определить, следует ли их запускать повторно или нет.
добавить данные для использованияMemo из взлома функции Async / Promise
Прежде всего, 90% заслуги принадлежит ---> Торвину .
Мне нужно было для моего проекта установить некоторые данные с помощью 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:
Передайте функцию «создать» и массив зависимостей. useMemo будет пересчитывать мемоизированное значение только при изменении одной из зависимостей. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендере.
Итак, эта строка:
const _statusMemo = useMemo(() => status, [status]);
В основном действует как слушатель , как только функция Async useEffect присваивает значение
status
, useMemo запускается и устанавливает для него свое значение.