【问题标题】:Using async-await in setState在 setState 中使用 async-await
【发布时间】:2021-06-05 04:41:56
【问题描述】:

我想进行基于当前状态的 API 调用,但无法使 setState 函数作为异步函数工作。

    handlePage = (direction: number) => {
    this.setState( async (state) => {
        const tickets: Ticket[] = await api.getTickets(`?page=${state.pageNum + direction}`)
        const pageNum: number = state.pageNum + direction;
        return { pageNum, tickets };
    });
}

告诉我错误:

类型参数 '(state: Readonly) => Promise' 不可分配给“AppState |”类型的参数((prevState: Readonly, props: Readonly) => AppState | Pick | null) |选择<...> |空值'。 输入 '(state: Readonly) => Promise' 不可分配给类型 '(prevState: Readonly, props: Readonly) => AppState | Pick |空值'。 键入 'Promise' 不可分配给类型 'AppState | Pick |空值'。 键入 'Promise' 缺少类型 'Pick': 搜索、主题、pageNumts(2345)

如果我在 setState 方法之外获取数据,它会起作用,但我害怕对过时的页码进行 API 调用:

    handlePage = async (direction: number) => {
    const tickets: Ticket[] = await api.getTickets(`?page=${this.state.pageNum + direction}`)
    this.setState( (state) => {
        const pageNum: number = state.pageNum + direction;
        return { pageNum, tickets };
    });
}

【问题讨论】:

  • 通过声明 React 状态更新函数 async 它隐式返回一个 Promise 并且这似乎与您声明的状态类型声明不兼容。 “过时的页码”是什么意思? this.state.pageNum 将是调用 handlePage 时当前渲染周期的值。
  • 似乎 OP 提出了一个XY Problem
  • 如果 this.state.pageNum 没有用于渲染 UI 的任何部分而仅用于 API 调用,您可以使用 useRef 挂钩将其存储为 ref。
  • outdated page number?? 是什么意思
  • outdated page number 我的意思是如果我不使用以前的状态设置状态,多次快速点击可能无法更新状态,如react documentation中所述

标签: reactjs typescript async-await


【解决方案1】:

有很多方法可以处理这个问题。我同意这是一个 XY 问题,因为我的建议是重新构建它,这样您就不会遇到这个问题。

推荐:按页面排列的关键门票

我最好的建议是,您所在的州将来自所有以前加载的页面的票证存储在以页码为键的数组中。

当pageNum 更改您(有条件地)获取该页面的结果时。每个响应都知道它在状态中的位置,因此在存储它时它是否仍然是当前页面并不重要。如果pageNum 快速连续更改两次,您将执行两个调用并存储两个结果,但不会混淆哪个是正确结果,因为每个结果都与其页码相关联。

在访问当前页面的门票时,只需查找当前页码的门票数组即可。如果它仍在加载,它将是一个数组 Ticket[] 或 undefined。这是您以前没有的额外信息,因为以前您无法知道您展示的票是当前票还是上一页的剩余票。

另一个好处是,如果用户在页面之间来回切换,您无需重复 API 调用。您可以在获取之前检查您是否已经拥有该页面的数据。

import React from "react";

interface Ticket {
    // actual properties
}

interface State {
    tickets: Record<number, Ticket[] | undefined>;
    pageNum: number;
}

class MyComponent extends React.Component<{}, State> {
    state: State = {
        tickets: {},
        pageNum: 1,
    }

    handlePage = async (direction: number) => {
        const page = this.state.pageNum + direction;
        // can skip the API call if already loaded!
        if (this.state.tickets[page] !== undefined) {
            return;
        }
        const tickets: Ticket[] = await api.getTickets(`?page=${page}`);
        // we will use the prevState when saving the tickets
        this.setState(prevState => ({
            tickets: {
                ...prevState.tickets,
                [page]: tickets
            }
        }));
    }

    render() {
        // get the tickets for the current page
        const tickets = this.state.tickets[this.state.pageNum] ?? [];
        // might also want to distinguish between an empty result and an incomplete fetch
        const isLoading = this.state.tickets[this.state.pageNum] === undefined;

        return <div/>
    }
}

【讨论】:

  • 我的问题是可扩展性:我希望我的应用程序能够有效地工作,即使数据库存储了大量的票证,因此将整个数据库从 API 发送到每个客户端,不管想要的页码会很浪费。
  • 也许我措辞不当。此代码不预加载页面。您不会在需要之前加载每个页面,但是一旦加载了该页面,您就会无限期地持有它。
  • 但是您实际上并没有在代码中包含您调用 handlePage 的位置,所以我想从技术上讲它现在没有任何作用。
  • 我现在明白你的意思了,这更有意义:-) 谢谢。
【解决方案2】:

setState 仅执行该作业设置状态(并触发重新渲染:P);我认为您可以将功能更改为:

handlePage = async (direction: number) => {
    const tickets: Ticket[] = await api.getTickets(`?page=${this.state.pageNum + direction}`);
    
    this.setState({
        pageNum: this.state.pageNum + direction,
        tickets
    })
}

【讨论】:

  • 也许我不够清楚,但我想根据之前的状态设置状态的原因是为了避免更新失败,如react's documentation 中所述。因此,恐怕使用您的建议,页码不会更新,因为它的工作原理与计数器完全一样。
  • 任何时候你的下一个状态依赖于前一个状态,即像为当前状态添加一个方向,你绝对应该使用功能状态更新。此答案仅在大部分的情况下有效。
猜你喜欢
  • 2019-09-10
  • 1970-01-01
  • 1970-01-01
  • 2018-04-11
  • 2021-05-28
  • 2020-10-12
  • 1970-01-01
  • 1970-01-01
  • 2017-01-23
相关资源
最近更新 更多