场景相对简单:我们在远程服务器上进行了长时间运行的按需计算。我们想记住结果。即使我们从远程资源中异步获取,这也不是副作用,因为我们只想将此计算的结果显示给用户,并且我们绝对不希望在每次渲染时都这样做。
问题:似乎 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])
等待异步函数的结果并使用 React.useMemo 记忆结果的正确方法是什么?我在普通的 JS 中使用了常规的 Promise,但在这些类型的情况下仍然难以解决。
我尝试过其他方法,例如 memoize-one,但问题似乎是 this
由于 React 函数组件的工作方式 破坏了 memoization 上下文发生变化,这就是我试图使用 React.useMemo。
也许我正试图在此处的圆孔中安装一个方形钉 - 如果是这种情况,也很高兴知道这一点。现在我可能只是要推出我自己的记忆功能。
编辑:我认为部分原因是我在使用 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>);
}
原文由 Doug Denniston 发布,翻译遵循 CC BY-SA 4.0 许可协议
您真正想要的是在异步调用结束后重新渲染您的组件。仅靠记忆不会帮助您实现这一目标。相反,您应该使用 React 的状态 - 它会保留您的异步调用返回的值,并允许您触发重新渲染。
此外,触发异步调用是一个副作用,因此它不应该在渲染阶段执行 - 既不在组件函数的主体内部,也不在
useMemo(...)
内部,这也发生在渲染阶段。相反,所有副作用都应该在useEffect
内部触发。这是完整的解决方案:
这里我们调用
useEffect
里面的 async 函数。请注意,您不能在useEffect
异步内部进行整个回调 - 这就是为什么我们在内部声明一个异步函数load
并在不等待的情况下调用它。一旦
arg
s 改变之一,效果将重新运行 - 这是大多数情况下您想要的。因此,如果您在渲染时重新计算它们,请务必记住arg
s。执行setResult(undefined)
是可选的 - 您可能希望将上一个结果保留在屏幕上,直到获得下一个结果。或者您可能会执行类似setLoading(true)
之类的操作,以便用户知道发生了什么。使用
active
标志很重要。没有它,您将自己暴露于等待发生的竞争条件:第二个异步函数调用可能在第一个完成之前完成:setResult()
发生setResult()
再次发生,用过时的结果覆盖正确的结果并且您的组件最终处于不一致的状态。我们通过使用
useEffect
的清理功能来重置active
标志来避免这种情况:active#1 = true
,开始第一次通话active#1 = false
active#2 = true
,开始第二次通话setResult()
发生setResult()
不会发生,因为active#1
是false