首页 > 解决方案 > 在 setState 中使用 async-await

问题描述

我想进行基于当前状态的 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<{ pageNum: number; 票:票[];}>' 不可分配给“AppState |”类型的参数 ((prevState: Readonly, props: Readonly<{}>) => AppState | Pick<AppState, "tickets" | "search" | "theme" | "pageNum"> | null) | 选择<...> | 无效的'。输入 '(state: Readonly) => Promise<{ pageNum: number; 票:票[];}>' 不可分配给类型 '(prevState: Readonly, props: Readonly<{}>) => AppState | Pick<AppState, "票" | "搜索" | “主题” | "页码"> | 无效的'。键入 'Promise<{ pageNum: number; 票:票[];}>' 不可分配给类型 'AppState | Pick<AppState, "票" | "搜索" | "

如果我在 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 };
    });
}

标签: reactjstypescriptasync-await

解决方案


有很多方法可以处理这个问题。我同意这是一个 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/>
    }
}

推荐阅读