场景相对简单:我们有一个在远程服务器上进行的长时间运行的按需计算.我们想把结果记下来.尽管我们从远程资源异步获取数据,但这并不是一个副作用,因为我们只想将计算结果显示给用户,我们绝对不想在每次渲染时都这样做.

问题:这似乎是一种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存储结果的正确方法是什么.使用备忘录?我对普通JS使用了常规promise ,但在这种情况下仍然难以实现.

我try 过其他方法,比如memoize one,但问题似乎是this上下文因React函数组件的工作方式而改变,这就是我try 使用React的原因.使用备忘录.

也许我想把一个方形的钉子装进一个圆形的洞里——如果是这样的话,知道这一点也很好.现在,我可能只想推出我自己的记忆功能.

编辑:我想部分原因是我在记忆一中犯了一个不同的愚蠢错误,但我仍然有兴趣知道答案.备忘录

这里有一个片段——我们的 idea 不是直接在render方法中使用记忆结果,而是以事件驱动的方式(即单击"计算"按钮)作为参考.

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的状态——它将保留异步调用返回的值,并允许您触发重新呈现.

此外,触发异步调用是一种副作用,因此它不应该在渲染阶段执行——既不应该在组件函数的主体内部执行,也不应该在渲染阶段也会发生的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中的异步函数.请注意,您不能使useEffect中的整个回调都是异步的——这就是为什么我们在其中声明一个异步函数load,并在不等待的情况下调用它.

一旦arg秒中的一个改变,效果就会重新运行——这是大多数情况下你想要的.所以,如果你在渲染时重新计算,一定要记住arg.执行setResult(undefined)是可选的——您可能希望在屏幕上保留上一个结果,直到获得下一个结果.或者你可以做setLoading(true)这样的事情,让用户知道发生了什么.

使用active国旗很重要.如果没有它,您将面临等待发生的竞争条件:第二个异步函数调用可能会在第一个异步函数调用完成之前完成:

  1. 开始第一次通话
  2. 开始第二次通话
  3. 第二次呼叫结束,发生setResult()
  4. 第一次呼叫结束,setResult()再次发生,覆盖

组件最终会处于不一致的状态.我们通过使用useEffect的清理功能重置active标志来避免这种情况:

  1. set active#1 = true, 开始第一次通话
  2. arg更改,调用清理函数,设置为active#1 = false
  3. set active#2 = true, 开始第二次通话
  4. 第二次呼叫结束,发生setResult()
  5. 第一次通话结束,setResult()没有发生,因为active#1false

Reactjs相关问答推荐

useReducer和带有回调的useState之间是否有实际区别?

Tailwind不使用@apply classes构建,并强制使用类似于移动的viewport

关于同一位置的不同组件实例的react S规则被视为相同组件

在Map()完成React.js中的多个垃圾API请求后执行函数

更改滑块标记的文本 colored颜色 ./Reaction/MUI 5

无法使用Reaction日期选取器和Next.js设置初始日期

GitHub页面未正确显示Reaction应用程序网站

使用 React + Vite 范围化 CSS

是否可以直接在 jsx 标签上使用 CSS 定义的 colored颜色 ?

解决在React测试库中的act()警告

如何根据用户在react.js中的 Select , Select 多个心形图标?

React - 函数组件 - 点击两次问题处理

无法访问 usefocusEffect 中 const 的更新状态

在 React JS 中编辑表格数据

我正在通过 API(有效)从 MongoDB 传递数据.可视化但我不断收到此 TypeError: Cannot convert undefined or null to object

Cypress 组件测试不渲染react 组件(但它的内容)

在 Formik 表单中访问子组件的值

SonarCloud 代码覆盖率不适用于 Github Action

使用 useRef(uuid()) 的目的是什么?

在渲染不显示子元素之后,再次渲染时,子元素状态数据完全消失了.为什么会这样以及如何防止它发生?