在进行异步调用后设置状态应该放在哪里?

4

我正在尝试从DynamoDB中提取一些数据,将其存储在本地状态中,然后对其进行操作。我可以提取数据,但我认为我可能需要处理异步内容,因此我使用了async await。

async componentDidMount() {
//aws secret key stuff here
const ddb = new AWS.DynamoDB({ apiVersion: '2012-10-08' });
    const res = await ddb.scan(params, (err, data) => {
      if (err) {
        console.log(err, err.stack);
      } else {
        console.log(data);
      }
    });
    this.setState({ products: data })
}

我有几个问题。基本上,我想等到扫描完成后才能设置状态,以确保数据已成功提取,然后我可以设置状态。
首先,语法检查器提醒我不应在componentDidMount中设置状态,那么我应该在哪里设置呢?
其次,等待某些请求完成后再使用数据的正确方法是什么?
我在this.setState({products:data})处做了一个console.log(data),并记录了正确的信息。
但是当我尝试这样迭代我的数据时:
{this.state.products.Items.map((product) => (

我收到了"无法读取未定义的地图"的错误信息?

2个回答

1
在componentDidMount中设置状态会导致额外的渲染。(参考文献)

在此方法中调用setState()将触发额外的渲染,但它会在浏览器更新屏幕之前发生。这保证了即使在这种情况下render()将被调用两次,用户也不会看到中间状态。请谨慎使用此模式,因为它经常会导致性能问题。但是,在需要测量依赖于其大小或位置的内容之前渲染DOM节点的情况下(如模态框和工具提示),这可能是必要的。

在某些情况下,您必须在componentDidMount上调用一些API并设置状态。

我看到您需要更改2件事。似乎ddb.scan作为第二个参数获取回调函数,所以您不需要Promises的ascyn/await。

在这种情况下,您需要在回调函数内部使用setState。

ddb.scan(params, (err, data) => {
  if (err) {
    console.log(err, err.stack);
  } else {
    console.log(data);
    this.setState({ products: data });
  }
});

如果您在其他应用程序中使用Promises,可以使用async/wait,在await之后使用setState。
const res = await promisecall();
this.setState(res.stuff);

在访问产品数组之前,您应该检查它是否已经存在。

this.state.products.Items

在第一次渲染过程中未定义。componentDidMount 运行并设置它,但是...
this.state.products.Items.map

已经抛出错误。

做类似于

this.state.products && 
this.state.products.Items &&
this.state.products.Items.length > 0 &&
this.state.products.Items.map(etc...)

只有当 this.state.products.Items 存在时,它才会运行。您这样做是为了防止在 componentDidMount 中设置状态之前的第一次渲染失败。


当我在回调函数中设置状态时,由于不知道Items是什么,所以在渲染方法中的map()出现错误。 - The Walrus
好的,我已经加入了检查,但是即使在第二次渲染中,我仍然没有看到任何产品显示。 - The Walrus
我发现了一件事,我没有使用.length,所以现在我正在使用它,但是我得到了“无法获取未定义的长度”的错误。因为我认为在第一次渲染时它是未定义的,所以在尝试验证时会出错? - The Walrus
而且,由于某种原因,我无法进入此地图函数。 - The Walrus
1
好的,不错,基本上可以工作了。它只打印出一个产品,但我相信那一定是另一个问题,而不是当前的问题。所以我会认为这个问题已经解决了。非常感谢! - The Walrus
显示剩余7条评论

0

第一点:不要直接把componentDidMount设置成async,可以在componentDidMount中调用一个异步函数来解决linting问题。

第二点:由于ddb.scan方法会通过回调函数返回响应结果,所以不需要在它前面加await关键字。你可以在回调函数中设置状态(setState)。

第三点:由于componentDidMount是在render方法之后被调用的,因此如果初始状态为null/undefined,就会在render方法中出现错误。请确保将状态初始化为空数组或执行验证操作。

componentDidMount() {
    this.dynamoDBScan();
}

async dynamoDBScan() {
     //aws secret key stuff here
    const ddb = new AWS.DynamoDB({ apiVersion: '2012-10-08' });
    const res = await ddb.scan(params, (err, data) => {
      if (err) {
        console.log(err, err.stack);
      } else {
        this.setState({ products: data })
      }
    });


}

然后在渲染函数中

{
   this.state.products && 
   this.state.products.Items.length > 0 &&
   this.state.products.Items.map((product) => (

好的,但这只是防止它崩溃,那么我怎样才能获取页面上的产品呢? - The Walrus
把 setState 调用放在回调函数中难道不会在 render 方法中显示结果吗?你可以尝试在 render 方法中使用 console.log(this.state.products) - Shubham Khatri
我已经完成了,但它返回给我一个空数组,这是初始状态,所以我很困惑。因为我认为回调函数首先必须执行? - The Walrus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接