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

document is not defined when attempting to setState from the return of an async call in componentWillMount

我在我的组件的 componentWillMount 调用中获取我的数据 [实际上它在一个 mixin 中,但想法相同]。在 ajax 调用 returns 之后,我尝试设置状态,但我收到未定义文档的错误。

我不确定如何解决这个问题。有什么值得等待的吗?我应该在其中执行 setState 的承诺或回调?

这就是我想要做的:

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

在一个简单的组件中进行尝试,以下工作正常:

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

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

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

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

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));
}


编辑: 忘了提...如果组件在异步操作完成之前得到 "unmounted",您也可能会遇到错误。

如果异步操作是 "cancelable",这很容易处理。假设您上面的 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 documentation 以获得其功能的完整列表,以及对我在上面简要描述的功能的更全面的解释。

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

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

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

有些人说您可以在 setTimeout 内执行此操作,他们是正确的,但这基本上是因为您将请求所花费的时间增加到最少 x,这通常足以强制它在组件安装后执行 setState - 所以实际上你也可以在 componentDidMount 中执行你的 setState 而不是依赖你的 [=26= 中的组件安装] 定时器。

TL;DR 答案:

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

然而,在 componentWillMount 中异步使用 setState 是非常不明智的,因为它会根据您的异步任务所花费的时间让您面临潜在的竞争条件。使用 componentDidMount 代替并使用初始渲染来显示加载微调器或类似的 :)