componentDidMount() 未被调用,但仅在特定情况下被调用

componentDidMount() is not getting called but only in a particular circumstance

请注意:这是一个非常奇怪的问题。我会尽力清楚地解释这个问题。这发生在我的 ReactJs 应用程序中,使用 React Router

我有一些组件需要来自 URL 的某种类型的参数。我也有不依赖于参数的组件。

如果我转到不需要任何参数的组件,则没有任何问题。

所以,我去我的Home,然后Account List,这两个都不需要任何参数,我可以来回多次,没有问题。

但是,如果我转到一个使用参数的组件,然后尝试转到另一个使用参数的组件,则会出现错误。该错误表明应该加载的组件 react-router 未正确安装,这引发了一个错误,告诉我子组件所需的数据丢失。我得到的错误非常简单。他们只是简单地说:

this.props.something is required but undefined

这是我在 App.jsx 的路线:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Route, Switch, withRouter } from 'react-router-dom';

// Import components here
// Just showing one for brevity
import Home from '../components/home/Home';

import * as appActions from '../actions/app-actions';

class App extends Component {

   constructor(props) {

      super(props);
   }

   render() {

      return(

          <div>
              <Switch>
                 <Route exact path="/member" component={Home} />
                 <Route exact path="/member/accounts" component={Accounts} />
                 <Route exact path="/member/projects" component={ProjectsList} />
                 <Route path="/member/projects/profile/:id" component={ProjectProfile} />|
                 <Route exact path="/member/tasks" component={TasksList} />
                 <Route path="/member/tasks/profile/:id" component={TaskProfile} />
              </Switch>
          </div>
      );

   }
}

function mapStateToProps(state) {

    return {
        member: state.member.memberData
    }
}

function mapDispatchToProps(dispatch) {

    return {

        actions: bindActionCreators(appActions, dispatch)
    };
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));

正如您从路线中看到的那样,ProjectProfileTaskProfile 组件都需要 ID。此外,ProjectProfileTaskProfile 组件都是相当简单的父组件,它们做两件事:

  1. 调用 API 以在 componentDidMount() 事件中加载数据
  2. 他们还会根据用户的屏幕分辨率加载正确的子组件

这是我的 ProjectProfile 组件,TaskProfile 与此几乎相同。

import React, { Component } from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

// Actions
import * as projectProfileActions from '../../../actions/project-profile-actions';

// Components
import Desktop from './ProjectProfileDesktop';
import Mobile from './ProjectProfileMobile';

class ProjectProfile extends Component {

    constructor(props) {

        super(props);
    };

    componentDidMount() {

        const id = this.props.match.params.id;
        this.props.actions.getData(id);
    }

    render() { 

        return (
                <div className="height-100 width-100">
                    <div className="height-100 width-100 row row-clean">
                    {this.props.ui.isDesktop || this.props.ui.isTablet ? <Desktop />
                        : this.props.ui.isMobile ? <Mobile />
                        : null}
                    </div>
            </div>
        );
    }
}

function mapStateToProps(state) {
    return {
        ui: state.app.window
    };
}

function mapDispatchToProps(dispatch) {

    return {
        actions: bindActionCreators(projectProfileActions, dispatch)
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(ProjectProfile);

最奇怪的是,如果我一直在 ProjectProfile 和任何其他组件之间来回移动并远离 TaskProfile,则没有问题。我什至可以用不同的 ID 输入 ProjectProfile,一切正常。仅当我使用参数转到 另一个 组件时,这一切都失败了。我可以用 TaskProfile 做同样的事情。我可以用不同的 ID 继续点击 TaskProfile 或在 TaskProfile 和任何组件 之间来回切换而无需 参数,一切正常。

只有当我先转到ProjectProfile,然后尝试转到TaskProfile,反之亦然,才会出现错误。

错误只是告诉我 ProjectProfileDesktop 需要的数据不存在。

进一步检查表明,在加载带有参数的组件后,如果我转到带有参数的另一个组件,我只是没有点击第二个组件中的 componentDidMount() 方法。看起来 render() 方法中的某些东西导致了问题并阻止它转到 componentDidMount()。我不确定是什么问题,因为我没有收到任何错误。我在 render() 方法的顶部放了一个 debugger 。虽然我没有看到确切的错误,但流程告诉我肯定出了问题。

另请注意,我使用的是 withRouter。我已经看到一些涉及 withRouter 的问题,但同样,到目前为止我找不到任何具体的问题。

我还想提一下,我也有 Stripe 提供商包装我的 App 组件。不确定这是否也在这个问题中起作用。这是 render() 在我的 index.js 中的样子:

render(
   <Provider store={store}>
        <BrowserRouter history={browserHistory}>
          <StripeProvider apiKey="my_key">
              <App />
          </StripeProvider>
       </BrowserRouter>
   </Provider>,
   document.getElementById('root')
);

我希望能就此提出一些建议。

更新:

我观察到的一个行为是,在用参数加载一个组件后,当我用一个参数点击第二个组件时,我首先点击了 render() 方法,紧接着第一个 createElement,代码跳转到 ReactInstrumentation.debugTool.onBeginLifeCycleTimer -- 请参见下面的屏幕截图。

更新 2:

在这一点上,我确信问题不是由 react-router 引起的,因为我将所需的 ID 硬编码到组件中,这样我就不会依赖于 react-router 或还有什么可以得到的。

我还从 TaskProfileProjectProfile 组件的路由中删除了 /:id 参数。

我仍然遇到同样的行为。所以,有些东西阻止了 componentDidMount() 方法被调用。我也试过 componentDidUpdate() 看看它是否被调用,但没有。我在两个组件中都放置了 componentWillUnmount(),当我在应用程序的其他地方导航时,在两个组件中都调用了 componentWillUnmount(),因此有人可能会争辩说两个组件都可以卸载。

所以底线是 render() 方法中的某些东西阻止了 componentDidMount() 或任何其他生命周期方法被调用,但这只发生在这种特定情况下。

我很感激此时的任何建议!!!

ComponentDidMount 仅在安装组件时调用一次。当您使用道具进行 api 调用时,我会使用 componentWillReceiveProps 并检查道具,然后进行 api 调用。

如果你想检查你的流程是否使用了 componentDidMount,请使用 console.log 检查它。

此致!

这个问题我苦苦思索了一个多星期,终于解决了。不可否认,很多我认为我理解的概念在这个过程中变得更加清晰。

错误是由于我处理 "isLoading" 动画逻辑的逻辑错误——是的,就这么简单和愚蠢!!!

我真正想在这里分享的是我为将来可能遇到类似行为的每个人吸取的教训。

  1. 每次加载组件时都应点击 componentDidMount()。即使你来回加载同一个组件。但是,只有当您的组件首先被卸载时,您才会点击 componentDidMount()。如果该组件未卸载,您将不会在后续使用该组件时点击 componentDidMount()。因此,请仔细查看您的逻辑,看看如果您离开它,您的组件是否会被卸载。我认为查看是否发生这种情况的最简单方法是添加带有 debuggercomponentWillUnmount() 方法。如果您在离开组件时点击此方法,则表示它正在卸载。
  2. 在此过程中进行了大量研究,以了解进行 API 调用以获取数据的最佳位置,显然 componentDidMount() 仍然是执行此操作的最佳位置。话虽如此,您可能需要在 componentDidMount() 中添加额外的逻辑,以确保您不会进行任何不必要的 API 调用,因为您的数据可能仍在您的商店中。
  3. 在我的研究过程中,有人建议最好使用 componentWillReceiveProps() 方法让我的 API 调用来获取数据。这是个坏建议!事实上,componentWillReceiveProps()componentWillUpdate()componentWillMount() 方法已被弃用,因此我们不应使用它们有两个原因:未来兼容性和遵循最佳实践。以下是有关弃用它们的原因的更多信息:https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
  4. 也许最重要的教训是:问题的起因可能比您想象的要简单得多——通常是这样——而且代码中的逻辑可能是问题的起因——通常是这样!

希望这些课程能帮助其他人快速轻松地找到解决方案!