componentDidMount() 没有被调用,但仅在特定情况下被调用 [英] componentDidMount() is not getting called but only in a particular circumstance

查看:39
本文介绍了componentDidMount() 没有被调用,但仅在特定情况下被调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

提醒一下:这是一个非常奇怪的问题.我会尽力清楚地解释这个问题.这发生在我使用 React RouterReactJs 应用程序中.

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

如果我使用不需要需要任何参数的组件,则没有任何问题.

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

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

<块引用>

this.props.something 是必需的但未定义

这是我在 App.jsx 中的路由:

import React, { Component } from 'react';从'react-redux'导入{连接};从redux"导入 { bindActionCreators };import { Route, Switch, withRouter } from 'react-router-dom';//在这里导入组件//为简洁起见只显示一个从'../components/home/Home'导入Home;import * as appActions from '../actions/app-actions';类 App 扩展组件 {构造函数(道具){超级(道具);}使成为() {返回(<div><开关><路由精确路径="/member" component={Home}/><路由精确路径="/member/accounts" component={Accounts}/><路由精确路径="/member/projects" component={ProjectsList}/><Route path="/member/projects/profile/:id" component={ProjectProfile}/>|<路由精确路径="/member/tasks" component={TasksList}/><Route path="/member/tasks/profile/:id" component={TaskProfile}/></开关>

);}}函数 mapStateToProps(state) {返回 {成员:state.member.memberData}}函数 mapDispatchToProps(dispatch) {返回 {动作:bindActionCreators(appActions, dispatch)};}导出默认 withRouter(connect(mapStateToProps, mapDispatchToProps)(App));

从路由中可以看出,ProjectProfileTaskProfile 组件都需要 ID.此外,ProjectProfileTaskProfile 组件都是相当简单的父组件,它们做两件事:

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

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

import React, { Component } from 'react'从'react-redux'导入{连接};从redux"导入 { bindActionCreators };//动作import * as projectProfileActions from '../../../actions/project-profile-actions';//组件从./ProjectProfileDesktop"导入桌面;从./ProjectProfileMobile"导入移动设备;类 ProjectProfile 扩展组件 {构造函数(道具){超级(道具);};componentDidMount() {const id = this.props.match.params.id;this.props.actions.getData(id);}使成为() {返回 (<div className="height-100 width-100"><div className="height-100 width-100 row row-clean">{this.props.ui.isDesktop ||this.props.ui.isTablet ?<桌面/>: this.props.ui.isMobile ?<移动/>: 空值}

);}}函数 mapStateToProps(state) {返回 {用户界面:state.app.window};}函数 mapDispatchToProps(dispatch) {返回 {动作:bindActionCreators(projectProfileActions, dispatch)};}导出默认连接(mapStateToProps,mapDispatchToProps)(ProjectProfile);

最奇怪的是,如果我一直在 ProjectProfile 和任何其他组件之间来回切换,并且远离 TaskProfile,则没有问题.我什至可以使用不同的 ID 来点击 ProjectProfile 并且一切正常.只有当我使用参数转到另一个组件时,这一切才会失败.我可以用 TaskProfile 做同样的事情.我可以用不同的 ID 继续点击 TaskProfile 或者在 TaskProfile 和任何组件没有参数之间来回切换,一切正常.

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

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

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

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

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

render(<提供者商店={商店}><BrowserRouter history={browserHistory}><StripeProvider apiKey="my_key"><应用程序/></StripeProvider></BrowserRouter></提供者>,document.getElementById('root'));

我很感激这方面的一些指示.

更新:

我观察到的一个行为是,在加载带有参数的组件后,当我使用参数点击第二个组件时,我首先点击了 render() 方法,然后是第一个 render() 方法code>createElement,代码跳转到ReactInstrumentation.debugTool.onBeginLifeCycleTimer——见下面的截图.

更新 2:

此时,我确信问题不是由 react-router 引起的,因为我将所需的 ID 硬编码到组件中,这样我就不再依赖于 react-router 或其他任何东西来获取它.

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

我仍然有同样的行为.所以,有些东西阻止了 componentDidMount() 方法被调用.我还尝试了 componentDidUpdate() 来查看它是否被调用,而它不是.我在两个组件中都放置了 componentWillUnmount(),当我在应用程序的其他地方导航时,两个组件中的 componentWillUnmount() 都被调用,因此人们可能会争辩说这两个组件都可以卸载.

所以最重要的是 render() 方法中的某些内容阻止了 componentDidMount() 或任何其他生命周期方法被调用,但这仅发生在此特殊情况.

我希望在这一点上有任何建议!!!

解决方案

我在这个问题上纠结了一个多星期,终于解决了.诚然,很多我以为我理解的概念在这个过程中变得更加清晰.

这个错误是由于我处理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. 也许最重要的教训是:问题的原因可能比您想象的要简单得多——通常是——而代码中的逻辑可能是问题的原因——通常是!

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

Just a heads up: this is a really strange problem. I'll do my best to clearly explain the issue. This is happening in my ReactJs app using React Router.

I have some components that require some type of parameter coming from the URL. I also have components that do NOT depend on a parameter.

If I go to components that do not require any parameters, there are no problems whatsoever.

So, I go to my Home, then Account List, neither of which require any parameters, I can go back and forth as many times as I like, there's no problem.

However, if I go to a component that uses a parameter, then try to go another component that uses a parameter, I then get an error. The error indicates that the component react-router is supposed to load is NOT mounted properly which throws an error telling me that the data the child component needs is missing. The errors I'm getting are pretty simple ones. They simply say something like:

this.props.something is required but undefined

Here's my routes in 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));

As you can see from the routes, both ProjectProfile and TaskProfile components require ID's. Furthermore, both ProjectProfile and TaskProfile components are rather simple parent components that do two things:

  1. Make an API call to load data in the componentDidMount() event
  2. They also load the correct child component based on user's screen resolution

Here's my ProjectProfile component and the TaskProfile is pretty much identical to this.

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

The strangest part about this is that if I keep going back and forth between ProjectProfile and any other component and stay away from TaskProfile, there are no problems. I can even hit ProjectProfile with different ID's and everything works fine. It all fails only when I go to another component with parameter. I can do the same with TaskProfile. I can keep hitting TaskProfile with different ID's OR go back and forth between TaskProfile and any component without a parameter and everything works fine.

Only if I go to ProjectProfile first, then try to go to TaskProfile or vice versa, the error occurs.

The error is simply telling me that the data ProjectProfileDesktop requires is not there.

Further inspection shows me that after loading a component with a parameter, if I go to another component with a parameter, I'm just NOT hitting the componentDidMount() method in the second component. Looks like something in the render() method is causing an issue and preventing it from going to componentDidMount(). I'm not sure what the issue is because I'm not getting any errors. I put a debugger at the top of the render() method. Though I'm not seeing an exact error, the flow shows me that something is definitely going wrong.

Please also notice that I'm using withRouter. I've seen some issues involving withRouter but again, I couldn't find anything concrete so far.

I also want to mention that I also have Stripe provider wrapping my App component. Not sure if this is playing a role in this problem as well. Here's what the render() looks like in my index.js:

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

I'd appreciate some pointers on this.

UPDATE:

A behavior I'm observing is that after loading a component with a param, when I hit the second component with a param, I hit the render() method first and right after the first createElement, the code jumps to ReactInstrumentation.debugTool.onBeginLifeCycleTimer -- see screen shot below.

UPDATE 2:

At this point, I'm convinced the issue is not caused by react-router because I hard-coded the ID I needed into the component so that I don't depend on react-router or anything else to get it.

I also removed /:id parameter from the routes for my TaskProfile and ProjectProfile components.

I still get the same behavior. So, something is preventing componentDidMount() method from being called. I also tried componentDidUpdate() to see if it was being called and it is NOT. I placed componentWillUnmount() in both components and as I navigate elsewhere in the app, in both components componentWillUnmount() gets called so one could argue that both components are unmounting OK.

So the bottom line is that something in the render() method is preventing componentDidMount() or any other lifecycle method from being called but this is only happening in this particular circumstance.

I'd appreciate any suggestions at this point!!!

解决方案

It's been more than a week I've been banging my head against the wall on this issue and it's finally solved. Admittedly, lots of concepts that I thought I understood became even clearer in the process.

The error was due a logical error on my part that handled "isLoading" animation logic -- yes, as simple and stupid as that!!!

What I really wanted to share here are the lessons I've learned for everyone who may experience a similar behavior in the future.

  1. You should hit componentDidMount() every time you load your component. Even if you go back and forth to load the same component. But you hit componentDidMount() ONLY IF your component was unmounted in the first place. If the component does not get unmounted, you will NOT hit componentDidMount() in your subsequent uses of the component. So look at your logic carefully to see if your component gets unmounted if you navigate away from it. I think the easiest way to see if this is happening is by adding componentWillUnmount() method with a debugger in it. If you're hitting this method when you navigate away from your component, that means it is unmounting.
  2. Did lots of research during this process to see where the best place to make API calls to fetch data and it's definitely clear that componentDidMount() is still the best place to do it. Having said that you may need additional logic in your componentDidMount() to make sure you don't make any unnecessary API calls because your data may still be in your store.
  3. During my research, some suggested that it's better to use componentWillReceiveProps() method to make my API call to fetch data. This is bad advice! As a matter of fact, componentWillReceiveProps(), componentWillUpdate() and componentWillMount() methods are being deprecated so we should not use them for two reasons: future compatibility and following best practices. Here's more info on why they're being deprecated: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
  4. Maybe the most important lesson is this: the cause of the problem may be much simpler than you think -- it usually is -- and the logic in your code may be the cause of the issue -- it usually is!

Hope these lessons will help others find their solutions quickly and painlessly!

这篇关于componentDidMount() 没有被调用,但仅在特定情况下被调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆