【问题标题】:Asynchronous call in componentWillMount finishes after render methodcomponentWillMount 中的异步调用在 render 方法后完成
【发布时间】:2017-04-09 21:40:02
【问题描述】:

我正在尝试在 componentWillMount 方法中对 API 执行异步调用。实际上,我希望在 componentWillMount 方法之后执行 render 方法,因为我需要将 props 传递给我的 render 方法中的组件。

这是我的代码:

class TennisSearchResultsContainer extends React.Component {
  componentWillMount () {
    // TODO: Build markers for the map
    // TODO: Check courtsResults object and database for tennis court
    this.courtsMarkers = this.props.courtsResults.map((court) => {
      return new google.maps.Marker({
        position: new google.maps.LatLng(JSON.parse(court.LOC).coordinates[1], JSON.parse(court.LOC).coordinates[0]),
        title: court.NAME,
        animation: google.maps.Animation.DROP
      });
    });
  }
  render () {
    return <TennisSearchResults criterias={this.props.criterias} courtsMarkers={this.courtsMarkers} />;
  }
}

我不明白为什么我的渲染方法似乎不等待异步调用完成并将未定义的道具传递给我的子组件...

我说的对吗?我应该怎么做才能解决这个问题?有什么办法处理呢?

【问题讨论】:

  • 你需要使用componentDidMount,因为componentWillMount在组件被挂载到DOM之前只运行一次。因此,不能保证组件会在 AJAX 调用之后呈现。
  • 你的代码的哪一部分是异步的?有很多谷歌地图调用,不清楚是其中一个,还是其他什么。

标签: javascript reactjs asynchronous


【解决方案1】:

您可能需要更好地了解 javascript 异步行为。异步意味着“不要等待”。该任务将在后台发生,其他代码将继续执行。管理此问题的一个好方法是在组件上设置状态。例如,当您输入componentDidMount 时,将loading 状态设置为true。然后当你的异步函数完成时,将该状态设置为false。然后,在您的 render 函数中,您可以显示“正在加载...”消息或数据。

这里有一些代码展示了一个异步获取数据的简化示例以及如何在 React 中处理它。在浏览器中打开开发者工具并查看控制台输出以更好地了解 React 生命周期。

编辑:截至 2018 年 4 月,代码已更新为使用新的 React 生命周期建议。总之,我将 componentWillMount 替换为更安全的 componentDidMount

正如“组件DIDmount”正确暗示的那样,组件已经挂载之后更新状态似乎效率低下。但是,根据official React documentation on componentDidMount

“如果您需要从远程端点加载数据,这是一个实例化网络请求的好地方。”

"在这个方法中调用setState()会触发额外的渲染,但是它会在浏览器更新屏幕之前发生。这保证了即使在这种情况下render()会被调用两次,用户也不会查看中间状态。”

这是完整的示例代码:

class MyComponent extends React.Component {
  constructor(props) {
    super();

    console.log('This happens 1st.');

    this.state = {
      loading: 'initial',
      data: ''
    };

  }

  loadData() {
    var promise = new Promise((resolve, reject) => { 
      setTimeout(() => {
        console.log('This happens 6th (after 3 seconds).');
        resolve('This is my data.');
      }, 3000);
    });

    console.log('This happens 4th.');

    return promise;
  }

  componentDidMount() {

    console.log('This happens 3rd.');

    this.setState({ loading: 'true' });
    this.loadData()
    .then((data) => {
      console.log('This happens 7th.');
      this.setState({
        data: data,
        loading: 'false'
      });
    });
  }  

  render() {

    if (this.state.loading === 'initial') {
      console.log('This happens 2nd - after the class is constructed. You will not see this element because React is still computing changes to the DOM.');
      return <h2>Intializing...</h2>;
    }


    if (this.state.loading === 'true') {
      console.log('This happens 5th - when waiting for data.');
      return <h2>Loading...</h2>;
    }

    console.log('This happens 8th - after I get data.');
    return (
      <div>
        <p>Got some data!</p>
        <p>{this.state.data}</p>
       </div>
    );
  }
}

ReactDOM.render(
  <MyComponent />,
  document.getElementsByClassName('root')[0]
);

这里是working example on CodePen

最后,我认为现代 React 生命周期created by React maintainer Dan Abramov 的这张图片有助于将发生的事情和时间可视化。

注意,从 React 16.4 开始,这个生命周期图有一个小的不准确之处:getDerivedStateFromProps 现在也在 setStateforceUpdate 之后调用。请参阅 React 官方博客中关于 Bugfix for getDerivedStateFromProps 的这篇文章

这个由 Wojciech Maj 创建的 interactive version of the React lifecycle diagram 允许您选择具有最新行为的 React 版本 >16.04(截至 2019 年 3 月 27 日的 React 16.8.6 仍然准确)。确保选中“显示不太常见的生命周期”选项。

【讨论】:

  • 请注意,从 React 16.4 开始,这个生命周期图有一个小的不准确之处:getDerivedStateFromProps 现在也在 setState(以及 forceUpdate?)之后调用。见reactjs.org/blog/2018/05/23/…
  • @MattKramer 感谢您指出这一点。我已更新答案以包含该注释。
【解决方案2】:

上面的答案对于您正在尝试做的事情可能有点矫枉过正。您需要做的就是让 compoentDidMount 成为一个异步函数。然后,您可以在返回 Promise 的函数调用上使用 await 关键字。

class MyComponent extends React.Component {

  async componentWillMount () {
  
    await myAsyncCall();
    
  }
  
  render () {
    
  }

}

【讨论】:

  • await 关键字不是将异步函数转换为同步函数的方法。为此,您需要多线程和并发机制。如果您删除了 asyncawait 关键字,您的代码将同样工作。您的代码相当于:class MyComponent extends React.Component { componentWillMount () { myAsyncCall().then(() =&gt; {}); /*do nothing*/ } render () { } }
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-10
  • 2021-01-27
  • 1970-01-01
  • 2020-09-08
  • 1970-01-01
  • 2011-09-07
  • 1970-01-01
相关资源
最近更新 更多