componentDidUpdate() 内部的 setState()

setState() inside of componentDidUpdate()

我正在编写一个脚本,它根据下拉菜单的高度和输入在屏幕上的位置将下拉菜单移动到输入的下方或上方。我还想根据其方向将修饰符设置为下拉列表。 但是在 componentDidUpdate 内部使用 setState 会创建一个无限循环(这是显而易见的)

我找到了使用 getDOMNode 并直接将类名设置为下拉列表的解决方案,但我觉得使用 React 工具应该有更好的解决方案。有人可以帮助我吗?

这是工作代码的一部分 getDOMNode(我 稍微忽略了定位逻辑以简化代码)

let SearchDropdown = React.createClass({
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        el.classList.remove('dropDown-top');
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            el.classList.add('dropDown-top');
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        return (
            <DropDown >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

这里是带有 setstate 的代码(它创建了一个无限循环)

let SearchDropdown = React.createClass({
    getInitialState() {
        return {
            top: false
        };
    },
    componentDidUpdate(params) {
        let el = this.getDOMNode();
        if (this.state.top) {
           this.setState({top: false});
        }
        if(needToMoveOnTop(el)) {
            el.top = newTopValue;
            el.right = newRightValue;
            if (!this.state.top) {
              this.setState({top: true});
           }
        }
    },
    render() {
        let dataFeed = this.props.dataFeed;
        let class = cx({'dropDown-top' : this.state.top});
        return (
            <DropDown className={class} >
                {dataFeed.map((data, i) => {
                    return (<DropDownRow key={response.symbol} data={data}/>);
                })}
            </DropDown>
        );
    }
});

您可以在 componentDidUpdate 中使用 setState。问题是你以某种方式创建了一个无限循环,因为没有中断条件。

基于组件呈现后您需要浏览器提供的值这一事实,我认为您使用 componentDidUpdate 的方法是正确的,它只需要更好地处理触发setState.

如果您在 componentDidUpdate 中使用 setState,它会更新组件,导致调用 componentDidUpdate,随后再次调用 setState,导致无限循环。您应该有条件地调用 setState 并确保违反调用的条件最终发生,例如:

componentDidUpdate: function() {
    if (condition) {
        this.setState({..})
    } else {
        //do something else
    }
}

如果你只是通过向它发送道具来更新组件(它不会被 setState 更新,除了 componentDidUpdate 中的情况),你可以在 componentWillReceiveProps 中调用 setState componentDidUpdate 个。

我遇到了类似的问题,我必须将工具提示居中。 componentDidUpdate 中的 React setState 确实让我陷入了无限循环,我尝试了它工作的条件。但我发现在 ref 回调中使用给了我更简单和干净的解决方案,如果你使用内联函数进行 ref 回调,你将面临每个组件更新的 null 问题。所以在 ref 回调中使用函数引用并在那里设置状态,这将启动重新渲染

我会说您需要检查状态是否已经具有您尝试设置的相同值。如果相同,则没有必要为相同的值再次设置状态。

确保像这样设置你的状态:

let top = newValue /*true or false*/
if(top !== this.state.top){
    this.setState({top});
}

componentDidUpdate 签名是 void::componentDidUpdate(previousProps, previousState)。有了这个,您将能够测试哪些 props/state 是脏的,并相应地调用 setState

示例:

componentDidUpdate(previousProps, previousState) {
    if (previousProps.data !== this.props.data) {
        this.setState({/*....*/})
    }
}

此示例将帮助您理解 React 生命周期钩子

您可以 setStategetDerivedStateFromProps 方法中,即 static 并在 componentDidUpdate.

中的道具更改后触发该方法

componentDidUpdate 中,您将获得 3rd 参数,其中 returns 来自 getSnapshotBeforeUpdate

你可以查看这个codesandbox link

// Child component
class Child extends React.Component {
  // First thing called when component loaded
  constructor(props) {
    console.log("constructor");
    super(props);
    this.state = {
      value: this.props.value,
      color: "green"
    };
  }

  // static method
  // dont have access of 'this'
  // return object will update the state
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps");
    return {
      value: props.value,
      color: props.value % 2 === 0 ? "green" : "red"
    };
  }

  // skip render if return false
  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    // return nextState.color !== this.state.color;
    return true;
  }

  // In between before real DOM updates (pre-commit)
  // has access of 'this'
  // return object will be captured in componentDidUpdate
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return { oldValue: prevState.value };
  }

  // Calls after component updated
  // has access of previous state and props with snapshot
  // Can call methods here
  // setState inside this will cause infinite loop
  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate: ", prevProps, prevState, snapshot);
  }

  static getDerivedStateFromError(error) {
    console.log("getDerivedStateFromError");
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    console.log("componentDidCatch: ", error, info);
  }

  // After component mount
  // Good place to start AJAX call and initial state
  componentDidMount() {
    console.log("componentDidMount");
    this.makeAjaxCall();
  }

  makeAjaxCall() {
    console.log("makeAjaxCall");
  }

  onClick() {
    console.log("state: ", this.state);
  }

  render() {
    return (
      <div style={{ border: "1px solid red", padding: "0px 10px 10px 10px" }}>
        <p style={{ color: this.state.color }}>Color: {this.state.color}</p>
        <button onClick={() => this.onClick()}>{this.props.value}</button>
      </div>
    );
  }
}

// Parent component
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 1 };

    this.tick = () => {
      this.setState({
        date: new Date(),
        value: this.state.value + 1
      });
    };
  }

  componentDidMount() {
    setTimeout(this.tick, 2000);
  }

  render() {
    return (
      <div style={{ border: "1px solid blue", padding: "0px 10px 10px 10px" }}>
        <p>Parent</p>
        <Child value={this.state.value} />
      </div>
    );
  }
}

function App() {
  return (
    <React.Fragment>
      <Parent />
    </React.Fragment>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

您可以在 componentDidUpdate

中使用 setState

this.setState 在 ComponentDidUpdate 中使用时在循环中没有中断条件时会创建一个无限循环。 你可以使用 redux 在 if 语句中设置一个变量 true 然后在条件中设置变量 false 就可以了。

像这样。

if(this.props.route.params.resetFields){

        this.props.route.params.resetFields = false;
        this.setState({broadcastMembersCount: 0,isLinkAttached: false,attachedAffiliatedLink:false,affilatedText: 'add your affiliate link'});
        this.resetSelectedContactAndGroups();
        this.hideNext = false;
        this.initialValue_1 = 140;
        this.initialValue_2 = 140;
        this.height = 20
    }