尝试从 componentWillMount 中异步调用的返回设置状态时未定义文档

IT技术 reactjs node-request
2021-05-11 04:53:17

我在我的组件的 componentWillMount 调用中获取我的数据[实际上它在一个混合中,但同样的想法]。ajax 调用返回后,我尝试 setState,但收到文档未定义的错误。

我不知道如何解决这个问题。有什么可以等的吗?我应该在其中执行 setState 的Promise或回调?

这就是我想要做的:

componentWillMount: function() {
    request.get(this.fullUrl()).end(function(err, res) {
        this.setState({data: res.body});
    }.bind(this));
}
3个回答

在内部做一些异步的事情不是一个好主意componentWillMount你真的应该这样做,componentDidMount因为如果一个组件做的第一个任务是获取一些数据 - 你可能希望它在获取数据之前显示一个微调器或某种加载通知程序。

因此,我个人从不做你正在做的事情,componentDidMount每次都选择然后,您可以设置初始状态,以便第一个安装渲染向用户显示加载微调器或某种其他类型的初始状态。ajax 触发,您在收到响应后进行更新。通过这种方式,您知道您正在处理用户连接不良的情况,例如接收不良的移动设备等,通过让用户知道组件正在加载一些数据来提供良好的用户体验,这就是他们不加载的原因”还没有看到任何东西。

说了这么多,为什么在内部执行一些异步函数时会出错componentWillMount- 因为如果你只是this.setState在生命周期函数内部调用,它会正常工作吗?这归结为 React 工作方式的一个怪癖,据我所知,它至少从 React 0.11 开始就已经存在了。当您挂载一个组件时,this.setState在内部同步执行componentWillMount工作正常(尽管 0.12.x 中存在一个错误,其中传递给setState内部的任何函数回调componentWillMount都不会被执行)。这是因为 React 意识到您正在为尚未挂载的组件设置状态——这是您通常无法做到的——但它允许它在生命周期函数中使用,例如componentWillMount特别。但是,当您将其异步化时setState调用,它不再被特殊对待并且适用正常规则 - 您不能setState在未安装的组件上。如果您的 ajax 请求返回非常快,则完全有可能您的setState调用是在该componentWillMount阶段之后但在组件实际安装之前发生的- 并且您会收到错误消息。如果实际上您的 ajax 请求没有看起来那么快,比如说它花了一秒钟或更长时间,那么您可能不会注意到错误,因为您的组件很可能在一秒钟内完全安装,因此您的setState调用变成再次按正常规则有效。但是您基本上是在这里给自己一个竞争条件,请注意安全并使用componentDidMount它 - 因为我上面谈到的其他原因也更好。

有些人说您可以在 a 中执行此操作setTimeout并且他们是正确的,但这基本上是因为您将请求所需的时间增加到最小 x,这通常足以强制它setState在组件安装后执行-如此有效,您还不如一直在做您的setState内部工作,componentDidMount而不是依赖setTimeout计时器内的组件安装

TL;博士回答:

您可以同步setState进入componentWillMount,但不推荐这样做。理想情况下,您可以使用任何同步执行此操作的情况getInitialState

然而,使用setState异步 incomponentWillMount是非常不明智的,因为它会让你面临基于异步任务所需时间的潜在竞争条件。使用componentDidMount替代和使用,初步呈现,显示正在加载的旋转或类似的:)

其实我以前也遇到过类似的情况。我假设您遇到的错误是这样的:

Uncaught Error: Invariant Violation: replaceState(...): Can only update a mounted or mounting component.

该错误是由以下事实引起的:在React组件中,您无法在安装组件之前设置状态。因此,componentWillMount与其尝试在 中设置状态,不如在componentDidMount. 我通常会添加一张.isMounted()支票,只是为了更好地衡量。

尝试这样的事情:

componentDidMount: function () {
  request.get(this.fullUrl()).end(function(err, res) {
    if (this.isMounted()) {
      this.setState({data: res.body});
    }
  }.bind(this));
}


编辑:忘记提及...如果在异步操作完成之前组件被“卸载”,您也可能会遇到错误。

如果异步操作是“可取消的”,这很容易处理。假设您的request()上述内容类似于superagent请求(可取消),我将执行以下操作以避免任何潜在错误。

componentDidMount: function () {
  this.req = request.get(this.fullUrl()).end(function(err, res) {
    if (this.isMounted()) {
      this.setState({data: res.body});
    }
  }.bind(this));
},

componentWillUnmount: function () {
  this.req.abort();
}


编辑 #2:在我们的评论中,您提到您的意图是创建一个可以异步加载状态的同构解决方案。虽然这超出了原始问题的范围,但我建议您查看react-async开箱即用,它提供了 3 种工具,可以帮助您实现这一目标。

  1. getInitialStateAsync - 这是通过 mixin 提供的,它允许组件异步获取状态数据。

    var React = require('react')
    var ReactAsync = require('react-async')
    
    var AwesomeComponent = React.createClass({
      mixins: [ReactAsync.Mixin],
    
      getInitialStateAsync: function(callback) {
        doAsyncStuff('/path/to/async/data', function(data) {
          callback(null, data)
        }.bind(this))
      },
    
      render: function() {
         ... 
      }
    });
    
  2. renderToStringAsync() - 允许您渲染服务器端

    ReactAsync.renderToStringAsync(
      <AwesomeComponent />,
      function(err, markup, data) {
        res.send(markup);
      })
    );
    
  3. injectIntoMarkup() - 这将注入服务器状态以及标记以确保它在客户端可用

    ReactAsync.renderToStringAsync(
      <AwesomeComponent />,
      function(err, markup, data) {
        res.send(ReactAsync.injectIntoMarkup(markup, data, ['./app.js']));
      })
    );
    

react-async提供比这更多的功能。您应该查看react-async 文档以获取其功能的完整列表,以及对我在上面简要描述的功能的更全面的解释。

在一个简单的组件中尝试一下,以下效果很好:

  getInitialState: function() {
    return {
      title: 'One'
    };
  },

  componentWillMount: function() {
    setTimeout(function(){
      this.setState({
        title: 'Two'
      });
    }.bind(this), 2000);
  },

您能否发布确切的错误信息,也许还发布堆栈跟踪信息,以便我们更好地了解您遇到的问题?