为什么我的组件在 setState 之前渲染?

Why does my component render before setState?

编辑 - 添加了 render 函数

我已经和这个问题抗争了几个星期了。我想创建两个显示在仪表板中的列表,都使用来自 API 的数据。如果我用虚拟数据创建一个 const,列表将根据需要显示。但是,当我尝试使用相同的代码来显示来自我的 API 的信息时,我在设置调用渲染的状态条件之前显示了一个列表。有一次我没有看到任何一个列表,所以我很高兴至少取得了一些进展。如果有人能协助指出我做错了什么,我将不胜感激!

async componentDidMount() {
    let data = {
      firstName: sessionStorage.getItem('cwsFirstName'),
      surname: sessionStorage.getItem('cwsSurname'),
      email: sessionStorage.getItem('cwsUser'),
      role: sessionStorage.getItem('cwsRole'),
      type: sessionStorage.getItem('cwsType'),
      storeId: sessionStorage.getItem('cwsStoreId'),
      clientId: sessionStorage.getItem('cwsClient')
    };

    let user = [];
    user.push(data);
    await this.setState({ user: user });
    const client = data.clientId;

    // start with extracting the services from the db
    const clientservices = await this.mysqlLayer.Get(`/admin/clientservices/${client}`);
    //console.log('clientservices: ', clientservices);

    let workzones = [
      {
        worklist: 'Queues',
        task: 'list_all'
      },
      {
        worklist: 'Today',
        task: 'list_today'
      }
    ];

    // loop through services to populate worklists
    let loopCount = 0;
    clientservices.forEach(async service => {
      let workspace = service.service;
      let type = service.type;

      let workspaces = [];

      // loop through the workzones
      workzones.forEach(async workzone => {
        //console.log('workzone: ', workzone);
        let worklists = [];
        let task = workzone.task;
        let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);
        //console.log('records: ', records);

        let statusArr = [];
        records.forEach(async record => {
          //console.log('currentStatus: ', record.currentStatus);
          statusArr.push(record.currentStatus);
        });

        let completeWorklist = statusArr.filter(this.onlyUnique);

        let statusList = [];
        if (completeWorklist.length > 0) statusList = this.filterWorklists(workspace, completeWorklist);

        let items = [];
        statusList.forEach(async element => {
          //console.log('element: ', element);
          let count = 0;
          records.forEach(async record => {
            if (record.currentStatus === element) {
              ++count;
            }
          });
          items.push({
            item: element,
            count: count
          });
          //console.log('items: ', items);
        }); // end of worklist loop *******************************

        worklists.push({
          worklist: workzone.worklist,
          items: items
        });
        //console.log('worklists: ', workzone, worklists, moment(new Date()).format('HH:mm:sss'));
        //console.log('worklists: ', workzone, worklists, moment(new Date()).milliseconds());

        await this.setState({
          records: records,
          type: type,
          worklists: [...this.state.worklists, ...worklists]
        });

        ++loopCount;
        console.log('loopCount: ', loopCount);

        //console.log('this.state.worklists: ', this.state.worklists, moment(new Date()).milliseconds());
        let tempWorklists = this.state.worklists;
        //console.log('tempWorklists: ', tempWorklists, moment(new Date()).milliseconds());

        workspaces.push({
          workspace: workspace,
          worklists: tempWorklists
        });


      }); // end of workzone loop *******************************


      //console.log('2 workspaces: ', workspaces);

      await this.setState({
        //loading: false,
        //workspaces: [...this.state.workspaces, ...workspaces]
        workspaces: workspaces
        //workspaces: workspacesConst
      });
      //console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());

      await this.setState({
        //loading: false,
        //workspaces: [...this.state.workspaces, ...workspaces]
        workspaces: workspaces
        //workspaces: workspacesConst
      });
      //console.log('this.state.workspaces: ', this.state.workspaces, moment(new Date()).milliseconds());

    }); // end of workspace loop *******************************

    // Should be good to render now
    //if (this.state.worklists.length > 1)
    await this.setState({ loading: false });
    //this.forceUpate();

  }

render() {

    if (this.state.loading) {
      return (
        <div>Loading...</div>
      );
    } else {

      const workspace = this.state.workspaces.map((workspace, idx) =>
      //const workspace = workspaces.map((workspace, idx) =>
        <div key={idx} className="card border-light mb-3" style={{padding: "20px"}}>
          {console.log('render workspace: ', workspace.worklists, moment(new Date()).milliseconds())}
          <Workspace
            key={idx}
            records={this.state.records}
            workspaces={workspace}
            type={this.state.type}
            user={this.state.user}
          />
        </div>
      );

      return (
        <div className="container">
          <div className="cols-12">
            <Welcome user={this.state.user}/>
            {workspace}
          </div>
        </div>
      );
    }
  }

如您所见,渲染发生在 this.state.loading 设置为 'false' 之前。当然它应该只在那之后渲染?

不,这不是 setState 和 React 生命周期的工作方式。
实际上每次你调用 setState react 都会自动调用 render 方法。 因此,在您的 componentDidMount 中,您多次调用 setState。所以它会多次渲染你的组件。并呈现您的工作区。
它不会停止,直到您最后一次调用 setState 来设置 loading false。
我建议你使用 progressbarspinner 或类似的东西来显示 loadingtrue 一旦它变成 false 你可以显示 workspaces.
这里还有一个注意事项是 setStateasynchronousawait 不能与 setState 一起使用,因为它不 return 任何 promise.
有关 React 生命周期的更多信息 -

这里有两个问题导致了我的问题:

    正如 Sagar 所指出的,
  1. await 不适用于 setState - 我之前不知道这一点
  2. await 具有 returns Promise 确实有效的功能(例如 API 调用),但代码将继续 运行 直到 Promise 完成。我知道这一点,但还没有完全意识到它对我尝试创建的序列意味着什么。

我通过向代码中的每一行添加一个 console.log 来了解这一点,以查看何时发生了什么。我开始意识到要求 API 提取的行是导致问题的原因:

let records = await this.mysqlLayer.Get(`/${type}/${workspace}/${task}/${client}`);

我的代码一直 运行 并更新状态以表明它已完成,因为 await 意味着它是并行的 运行 个部分。

我还没有修复代码,但想让其他人知道,以防他们遇到类似问题。