【问题标题】:React.js Updating state where multiple API endpoints are involvedReact.js 更新涉及多个 API 端点的状态
【发布时间】:2020-07-30 20:02:53
【问题描述】:

我目前正在尝试让一个项目工作以测试一些东西,但我正处于尝试正确更新状态的地步。

我有一个通过axios.get("/docker/containers") 访问的端点,它将为当前在我的系统上运行的容器的所有 ID 返回一个数组,如下所示:

componentDidMount() {
  this.interval = setInterval(() => this.updateContainers(), 3000);
};
componentWillUnmount() {
  clearInterval(this.interval);
}

此时我的状态如下:

state = {
  containers: [{id: 'id1'}, {id: 'id2'}]
}

然后用户界面只显示一个 ID 列表。

然后我可以在我的用户界面上单击一个 ID,它会设置一个观察者:

state = {
  containers: [{id: 'id1', watcher: true}, {id: 'id2'}]
}

观察者的目的是为了在下一个更新周期中检索有关特定容器的更详细信息。

state = {
  containers: [{id: 'id1', watcher: true, name: 'container1'}, {id: 'id2'}]
}

单击用户界面中已设置watcher 的容器后,watcher 将被删除,然后不再检索更详细的信息

state = {
  containers: [{id: 'id1', watcher: false}, {id: 'id2'}]
}

我遇到的困难是如何获取更详细的信息。我的updateContainers 方法有3个步骤:

  1. 从 API 读取响应并将状态分解为单独的变量,将状态 var 与响应 var 进行比较,并删除所有已关闭的容器(此处未完成 setState)。

  2. 将响应中的任何新容器添加到此后出现的状态(同样没有setState)。

...到目前为止一切顺利...

  1. 循环遍历步骤 1 和 2 中过滤的容器数组,找到任何设置了 watcher 的容器。在设置位置执行 API 调用以检索更详细的信息。最后设置状态。

在第 3 步中,我在过滤后的数组上使用forEach,然后执行axios.get("/docker/containers/id1"),其中设置了watcher,否则只需保留我已经拥有的容器详细信息,但这就是我卡住的地方, Typescript 也给了我错误:

TS2322: Type 'void' is not assignable to type 'IndividualContainer[]'.

目前我有:

updateContainers() {
  axios.get('/docker/containers')
    .then(response => {
       const apiRequestedContainers: string[] = response.data.containers;
       // array of only IDs
       const stateContainers: IndividualContainer[] = [
         ...this.state.containers
       ];
       // remove dead containers from state by copying still live containers
       let filteredContainers: IndividualContainer[] = [
         ...this.filterOutContainers(stateContainers, apiRequestedContainers)
       ];
       // add new containers
       filteredContainers = this.addContainerToArray(
         filteredContainers, apiRequestedContainers
       );
       return this.updateContainer(filteredContainers);
     })
     .then(finalArray => {
       const newState: CState = {'containers': finalArray};
       this.setState(newState);
     });
};

updateContainer(containers: IndividualContainer[]) {
  const returnArray: IndividualContainer[] = [];
  containers.forEach(container => {
    if (container.watcher) {
      axios.get('/docker/containers/' + container.id)
        .then(response => {
          // read currently available array of containers into an array
          const resp = response.data;
          resp['id'] = container.id;
          resp['watcher'] = true;
          returnArray.push(resp);
        });
    } else {
      returnArray.push(container);
    }
    return returnArray;
  });
};

任何指向我的逻辑失败的地方都将不胜感激!

编辑:

渲染方法:

render() {
  const containers: any = [];
  const curStateOfContainers: IndividualContainer[] = [...this.state.containers];
  if (curStateOfContainers.length > 0) {
    curStateOfContainers.map(container => {
      const container_id = container.id.slice(0, 12);
      containers.push(
        <Container
                   key          = {container_id}
                   container_id = {container.id}
                   name         = {container.name}
                   clickHandler = {() => this.setWatcher(container.id)}
        />
      );
    });
  }
  return containers;
}

【问题讨论】:

  • 请解释方法addContainerToArray
  • 在该方法中,我将容器从我的初始响应对象添加到状态。例如,在新容器上线的情况下。
  • 请解释一下这个方法。它应该返回一个 IndividualContainer 数组
  • 哪一行给出了 TS 错误?
  • 所以该方法取了2个数组,第一个数组是已经处于app状态的容器,第二个是最近一次get请求接收到的容器,get请求中任何不处于该状态的容器然后添加,两个数组中的任何容器都会从状态中保留,这是由于需要保留观察者参数。

标签: reactjs typescript api axios


【解决方案1】:

首先,我评论了您代码中的错误:

updateContainers() {
  axios.get('/docker/containers')
    .then(response => {
       ...
       return this.updateContainer(filteredContainers);
         // returns `undefined`...
     })
     .then(finalArray => { ... });
       // ...so `finalArray` is `undefined` - the reason for TS error
       // Also `undefined` is not a `Promise` so this second `then()`
       // doesn't make much sense
};

updateContainer(containers: IndividualContainer[]) {
  const returnArray: IndividualContainer[] = [];
  containers.forEach(container => {
    if (container.watcher) {
      axios.get('/docker/containers/' + container.id)
        .then(response => {
           ...
           returnArray.push(resp)
             // because `axios.get()` is asynchronous
             // this happens only some time after
             // `.then(finalArray => { ... })` is finished
        });
        // at this moment code inside `.then()` has not been executed yet
        // and `resp` has not yet been added to `returnArray`
    } else {
      returnArray.push(container)
        // but this happens while `forEach()` is running
    }
    return returnArray;
      // here you return from `forEach()` not from `updateContainer()`
      // also `forEach()` always returns `undefined`
      // so even `return containers.forEach(...)` won't work

  });
  // no return statement, that implicitly means `return undefined`
};

现在,为什么@RocKhalil 的回答有点有效:

async updateContainers() {
  const response = await axios.get('/docker/containers')
    // he favors a much clearer syntax of async/await
  ...
  const containers = await this.updateContainer(filteredContainers)
  this.setState({ containers });
};

async updateContainer(containers) {
  return containers.map(async (container) => {
    if (container.watcher) {
      const response = await axios.get('/docker/containers/' + container.id)
        // Because `axios.get()` was **awaited**,
        // you can be sure that all code after this line
        // executed when the request ended
        // while this
        //   axios.get(...).then(() => console.log(2)); console.log(1)
        // will lead to output 1 2, not 2 1
      return {
        ...response.data,
        id: container.id,
        watcher: true,
      }
    } else {
       return container;
    }
  });
  // he does not forget to return the result of `map()`
  // and `map()` in contrast with `forEach()` does have a result
  // But...
}

但是…… containers.map() 返回一个数组Promises 的数组。没有一个Promise。这意味着

  const containers = await this.updateContainer(filteredContainers)

什么都不等。而updateContainer() 函数实际上并不是async。 要解决这个问题,您需要使用 Promise.all():

  const containers = await Promise.all(this.updateContainer(filteredContainers))

【讨论】:

  • 感谢您的回答,以及包含的推理!
【解决方案2】:

问题是您没有从 updateContainer 方法返回任何内容,该方法将隐式返回 void

// This function return void
updateContainer(containers: IndividualContainer[]) {
  const returnArray: IndividualContainer[] = [];
  containers.forEach(container => {
    if (container.watcher) {
      axios.get("/docker/containers/" + container.id).then(response => {
        // read currently available array of containers into an array
        const resp = response.data;
        resp["id"] = container.id;
        resp["watcher"] = true;
        returnArray.push(resp);
      });
    } else {
      returnArray.push(container);
    }
    // this is inside the forEach callback function not updateContainer function
    return returnArray;
  });
}

然后你将void 分配给containers,它应该是IndividualContainer[] 类型,所以TypeScript 会给你一个错误,然后你将它设置为状态:

updateContainers() {
  axios
    .get("/docker/containers")
    .then(response => {
      const apiRequestedContainers: string[] = response.data.containers; // array of only IDs
      const stateContainers: IndividualContainer[] = [
        ...this.state.containers
      ];
      // remove dead containers from state by copying still live containers
      let filteredContainers: IndividualContainer[] = [
        ...this.filterOutContainers(stateContainers, apiRequestedContainers)
      ];
      // add new containers
      filteredContainers = this.addContainerToArray(
        filteredContainers,
        apiRequestedContainers
      );
      // this return void as well
      return this.updateContainer(filteredContainers);
    })
    // finalArray is void
    .then(finalArray => {
      // you assign void to containers which should be of type IndividualContainer[]
      const newState: CState = { containers: finalArray };
      // containers will be set to undefined in you state
      this.setState(newState);
    });
}

你的本意是这样做的:

// I added a return type here so that TypeScript would yell at me if I return void or wrong type
updateContainer(containers: IndividualContainer[]): IndividualContainer[] {
  const returnArray: IndividualContainer[] = [];
  containers.forEach(container => {
    if (container.watcher) {
      axios.get("/docker/containers/" + container.id).then(response => {
        // read currently available array of containers into an array
        const resp = response.data;
        resp["id"] = container.id;
        resp["watcher"] = true;
        returnArray.push(resp);
      });
    } else {
      returnArray.push(container);
    }
    // removed the return from here as it's useless
  });
  // you should return the array here
  return returnArray;
}

【讨论】:

  • 不客气。我很高兴这有帮助。如果您仍有任何问题,我很乐意提供帮助。
【解决方案3】:

我不是 TypeScript 方面的专家,所以我不得不更改对 JS 的响应,并认为您会在 TS 中重新编写它以备不时之需。

  async updateContainers() {
    const response = await axios.get('/docker/containers')
    const apiRequestedContainers = response.data.containers; // array of only IDs
    
    const stateContainers = [...this.state.containers];
    // remove dead containers from state by copying still live containers
    let filteredContainers = [...this.filterOutContainers(stateContainers, apiRequestedContainers)];
    // add new containers
    filteredContainers = this.addContainerToArray(filteredContainers, apiRequestedContainers);

    const containers = await this.updateContainer(filteredContainers)
    this.setState({ containers });
  };

  async updateContainer(containers) {
    return containers.map(async (container) => {
      if (container.watcher) {
        const response = await axios.get('/docker/containers/' + container.id)
        
        // read currently available array of containers into an array
        return {
          ...response.data,
          id: container.id,
          watcher: true,
        }
     } else {
        return container;
      }
    });
  }

这是我在 updateContainer 中更新的内容:

  • 我现在正在映射数组而不是执行 forEach
  • 我现在正在等待容器详细信息 API 返回值,然后再检查第二个容器。 --> 这是主要问题,因为您的代码不会等待 API 完成(等待/异步)

【讨论】:

  • 感谢您的回答。我已经实现了它,但是在渲染方法中出现错误: Uncaught TypeError: container.id is undefined render Containers.tsx:138 render Containers.tsx:137 React 11 stable_runWithPriority scheduler.development.js:653 React 6 updateContainers Containers.tsx:88 container = await... 的控制台日志显示了一系列承诺。
  • 我已将我的渲染方法添加到原始帖子中以防万一
  • @Oceanic_Panda 是的,因为您当前拥有的是 Promises 列表,而不是数组中的实际容器信息。您能否与我们分享文件,以便我们直接查看问题?查看具有潜在 line error 的代码块无助于解决问题
猜你喜欢
  • 2022-01-22
  • 2021-08-17
  • 1970-01-01
  • 2018-02-19
  • 1970-01-01
  • 1970-01-01
  • 2020-04-27
  • 2021-12-02
  • 1970-01-01
相关资源
最近更新 更多