componentWillReceiveProps和componentDidUpdate无限循环

时间:2017-07-23 21:57:17

标签: javascript reactjs ecmascript-6

我试图在从子组件更改状态后更新状态并且没有成功,每次我调用函数时我得到堆栈溢出,prop都调用无限次函数,但问题是,我真的需要更新这个状态,并且不知道目前如何解决这个问题。

import React, { PropTypes, Component } from 'react';
import Card from './card/card.js';
import style from './style.scss';

class Container extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isFlipped: false,
      oneOpened: true,
      history: [],
      childFlipToFalse: false,
    };
    this.historyToggleStates = this.historyToggleStates.bind(this);
    this.forceFlipParent = this.forceFlipParent.bind(this);
    this.checkForceFlip = false;
  }
  historyToggleStates(bool, id, callForceFlip) {
    this.setState({
      history: this.state.history.concat([{ opened: bool, id }]),
    }, () => {
      console.log('inside historyToggleStates');
      if (callForceFlip) {
        this.forceFlipParent()
      }
    });
  }
  forceFlipParent() {
    const { history } = this.state;
    const first = history[0];
    const last = history[history.length - 1];
    const beforeLast = history[history.length - 2];
    console.log('force FLIP PARENT');
    if (history.length > 1) {
      if (JSON.stringify(last.opened) === JSON.stringify(beforeLast.opened)) {
        this.setState({ childFlipToFalse: true });
      }
    }
  }
  render() {
    const rest = {
      basePath: this.props.basePath,
      backCard: this.props.backCard,
      isShowing: this.props.isShowing,
      historyToggleStates: this.historyToggleStates,
      isOpened: this.state.isOpened,
      isFlipped: this.state.isFlipped,
      checkOneOpened: this.checkOneOpened,
      history: this.state.history,
      forceFlip: this.state.childFlipToFalse,
      flipToFalse: this.forceFlipParent,

    };
    const cardsMap = this.props.cards.map((item, key) => {
      return (
        <Card
          item={item}
          keyId={key}
          {...rest}
        />
      );
    });
    return (
      <div className="col-lg-12 text-center">
        {cardsMap}
      </div>
    );
  }
}


export default Container;

Container.propTypes = {
  cards: PropTypes.array.isRequired,
  item: PropTypes.func,
  basePath: PropTypes.string,
  backCard: PropTypes.string,
  isShowing: PropTypes.bool,
};

儿童

import React, { Component, PropTypes } from 'react';
import ReactCardFlip from 'react-card-flip';
import style from './style.scss';

class Card extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isFlipped: false,
      update: false,
      id: 9999999,
    };
    this.handleClick = this.handleClick.bind(this);
    this.checkOneOpened = this.checkOneOpened.bind(this);
  }
  componentWillReceiveProps(nextprops) {
    const { history, isFlipped, historyToggleStates } = this.props;
    const last = nextprops.history[nextprops.history.length - 1];
    const beforeLast = nextprops.history[nextprops.history.length - 2];
    console.log(history);
    console.log(nextprops.history);
    if (nextprops.forceFlip && last.id === nextprops.keyId) {
      this.setState({ isFlipped: !this.state.isFlipped, update: true, id: last.id }, () => {
        console.log('callback willreceiveprops', this.state.isFlipped);
        historyToggleStates(this.state.isFlipped, nextprops.keyId, true, this.state.update);  **<--- Here's my problem**
      });
    }
    if (nextprops.forceFlip && beforeLast.id === nextprops.keyId) {
      this.setState({ isFlipped: !this.state.isFlipped, update: true, id: beforeLast.id }, () => {
      });
    }
  }

  handleClick(e, nextState, id) {
    const { keyId, historyToggleStates, forceFlip } = this.props;
    if (e) {
      e.preventDefault();
    }
    if (!nextState) {
      this.setState({ isFlipped: !this.state.isFlipped }, () => {
        historyToggleStates(this.state.isFlipped, keyId, true, this.state.update);
      });
    } else {
      // historyToggleStates(nextState, id, false);
      return 0;
    }
  }

  checkOneOpened(e) {
    if (!this.props.isShowing) {
      this.handleClick(e);
    }
  }

  render() {
    const { item, basePath, backCard, isShowing, isFlipped, forceFlip } = this.props;
    return (
      <div className={`col-lg-2 col-md-3 col-sm-6 ${style.card}`}>
        <ReactCardFlip
          isFlipped={this.state.isFlipped}
          flipSpeedBackToFront={0.9}
          flipSpeedFrontToBack={0.9}
        >
          <div key="front">
            <button
              onClick={() => {this.checkOneOpened()}}
            >
              <img src={isShowing ? `${basePath}${item.image}` : backCard} alt={item.name} className={`${style.img}`} />
            </button>
          </div>
          <div key="back">
            <button
              onClick={() => {this.checkOneOpened()}}
            >
              <img src={isShowing ? backCard : `${basePath}${item.image}`} alt={item.name} className={`${style.img}`} />
            </button>
          </div>
        </ReactCardFlip>
      </div>
    );
  }
}

export default Card;


Card.propTypes = {
  basePath: PropTypes.string,
  backCard: PropTypes.string,
  isShowing: PropTypes.bool,
  historyToggleStates: PropTypes.func,
  isOpened: PropTypes.bool,
  isFlipped: PropTypes.bool,
  checkOneOpened: PropTypes.func,
};

historyToggleStates(this.state.isFlipped,nextprops.keyId,true,this.state.update)是我的问题的根源,我真的需要更新它,因为我将他内部的数组与另一个数组进行比较阵列

更新1:我知道我对 historyToggleStates 的调用是在几个案例中完成的,但正如您所看到的,我需要从父级更新我的状态,因为我每次都在我的componentWillReceiprops中从我的子组件中比较这个值。

这种情况真的需要州政府经理吗?我遵循Dan Abramov的提示,并避免提高系统的复杂性,任何提示都会受到赞赏。

4 个答案:

答案 0 :(得分:3)

callForceFlip 为true的情况下,

您的调用 historyToggleStates ,这会导致调用父级的 forceFlipParent ,这会设置 childFlipToFalse True (作为 forceFlip 传递给孩子)

我相信 forceFlip 永远是真的是你问题的根源。

答案 1 :(得分:3)

子组件的componentWillReceiveProps,当父级用新的道具更新时会触发它,触发父级的更新(historyToggleStates)。

但是,此周期取决于以下内容:

if (nextprops.forceFlip && last.id === nextprops.keyId) {

即。如果组件的传入道具keyId与历史记录keyId中的最后一个组件相同。换句话说,当子项更新时,历史记录中的最后一个组件将始终运行此代码块,前提是forceFlip为真。

forceFlip的值取决于以下内容:

    if (history.length > 1) {
       if (JSON.stringify(last.opened) === JSON.stringify(beforeLast.opened)) {

即。如果历史记录中的最后两个组件同时打开,则forceFlip设置为true。

然后,正如您所说,historyToggleStates被触发,父级被更新,子级componentWillReceiveProps被触发,循环重复。

可能的解决方法

现在我认为它是故意设计的,如果最后两张牌同时打开,它们必须被强制翻转,即关闭。

为了达到这个目的,我要说不要将卡的状态保持在卡组件中,而是将其拉到父组件状态并保持子卡组件不可知。在父母中,维护cardsState(可能是更好的名字)之类的内容,并使用它来决定是否必须打开卡片。如果您检测到它们在历史记录中已打开,则可以通过简单地将其状态设置为false来关闭最后两张同时打开的卡片。

这将使您摆脱对forceFlip变量的依赖,并使您的子卡组件更简单 - 只有在状态为open时才打开它们。

答案 2 :(得分:1)

我有一个与此类似的问题,我的handleClick无限循环,请尝试使用onClick函数:

onClick={() => {this.checkOneOpened()}}

答案 3 :(得分:1)

  • 我认为问题出在本部分

    if (nextprops.forceFlip && last.id === nextprops.keyId) {
     this.setState({ isFlipped: !this.state.isFlipped, update: true, id: last.id }, () => {
     console.log('callback willreceiveprops', this.state.isFlipped);
     historyToggleStates(this.state.isFlipped, nextprops.keyId, true, this.state.update); // **<--- Heres my problem**
    });}
    

    您可以检查if条件的值,以便重新渲染后不会进入内部条件。

  • prop-types已移至另一个套餐,请结帐,以便您不会收到https://facebook.github.io/react/docs/typechecking-with-proptypes.html

  • 的警告
  • 建议将childFlipToFalse的名称属性更改为ischildFlip

  • 您可以在此处取出回拨功能,因为您没有使用它

    this.setState({ isFlipped: !this.state.isFlipped, update: true, id: beforeLast.id }, () => {});
    

    this.setState({ isFlipped: !this.state.isFlipped, update: true, id: beforeLast.id });
    

这是我的建议:在这种情况下,你可以让父组件成为唯一的类Componente,子组件是staless组件,这将有助于拥有更好的结构和管理你的子组件,这将使你做一个不同的工作这种情况将更容易接近