11

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

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

这就是我想要做的:

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

3 回答 3

6

在内部做一些异步的事情不是一个好主意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在组件安装后执行 -如此有效,您还不如一直在做自己的事情setStatecomponentDidMount而不是依赖setTimeout计时器中的组件安装。

TL;DR 答案:

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

但是,使用setState异步输入componentWillMount是非常不明智的,因为它会根据您的异步任务花费的时间为您打开潜在的竞争条件。改用componentDidMount并使用该初始渲染来显示加载微调器或类似内容:)

于 2015-05-01T21:01:55.727 回答
5

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

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 文档以获取其功能的完整列表,以及对我上面简要描述的功能的更全面的解释。

于 2015-04-29T01:33:53.230 回答
2

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

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

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

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

于 2015-04-28T23:03:34.837 回答