setState () ภายใน componentDidUpdate ()


133

ฉันกำลังเขียนสคริปต์ที่เลื่อนลงด้านล่างหรือด้านบนของอินพุตขึ้นอยู่กับความสูงของดรอปดาวน์และตำแหน่งของอินพุตบนหน้าจอ นอกจากนี้ฉันต้องการตั้งค่าตัวปรับเปลี่ยนเป็นแบบเลื่อนลงตามทิศทางของมัน แต่ใช้setStateภายในของการcomponentDidUpdateสร้างลูปที่ไม่มีที่สิ้นสุด (ซึ่งเห็นได้ชัด)

ฉันพบวิธีแก้ปัญหาในการใช้getDOMNodeและการตั้งชื่อคลาสเป็นแบบเลื่อนลงโดยตรง แต่ฉันรู้สึกว่าควรมีวิธีแก้ปัญหาที่ดีกว่าโดยใช้เครื่องมือ React ใครสามารถช่วยฉัน?

นี่คือส่วนหนึ่งของรหัสการทำงานด้วยgetDOMNode(ia ตรรกะการวางตำแหน่งที่ถูกละเลยเล็กน้อยเพื่อลดความซับซ้อนของรหัส)

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

9
ผมคิดว่าเคล็ดลับที่นี่เป็นที่setStateจะเสมอเรียกอีกครั้งทำให้ แทนที่จะตรวจสอบstate.topและเรียกsetStateหลายครั้งเพียงติดตามสิ่งที่คุณต้องการstate.topที่จะอยู่ในตัวแปรท้องถิ่นแล้วครั้งหนึ่งในตอนท้ายของcomponentDidUpdateการโทรเฉพาะในกรณีที่ตัวแปรท้องถิ่นของคุณไม่ตรงsetState state.topตามที่แสดงในตอนนี้คุณจะรีเซ็ตทันทีstate.topหลังจากการเรนเดอร์ใหม่ครั้งแรกซึ่งทำให้คุณอยู่ในวงวนที่ไม่มีที่สิ้นสุด
Randy Morris

2
ดูการใช้งานที่แตกต่างกันสองแบบcomponentDidUpdateในซอนี้
Randy Morris

ไอ้มัน! ตัวแปรท้องถิ่นช่วยแก้ปัญหาทั้งหมดได้อย่างไร mysef ก็คิดไม่ออก! ขอบคุณ!
Katerina Pavlenko

1
ฉันคิดว่าคุณควรยอมรับคำตอบด้านล่างนี้ หากคุณอ่านอีกครั้งฉันคิดว่าคุณจะพบว่ามันตอบคำถามเริ่มต้นได้เพียงพอ
Randy Morris

ทำไมไม่มีใครแนะนำให้ย้ายเงื่อนไขเข้าไปcomponentShouldUpdate?
Patrick Roberts

คำตอบ:


117

คุณสามารถใช้ภายในsetState componentDidUpdateปัญหาคือคุณกำลังสร้างลูปที่ไม่มีที่สิ้นสุดเนื่องจากไม่มีเงื่อนไขการหยุดพัก

จากข้อเท็จจริงที่ว่าคุณต้องการค่าที่เบราว์เซอร์ระบุเมื่อแสดงผลองค์ประกอบแล้วฉันคิดว่าแนวทางของคุณเกี่ยวกับการใช้งานcomponentDidUpdateนั้นถูกต้องเพียงแค่ต้องการการจัดการที่ดีขึ้นจากเงื่อนไขที่เรียกใช้ไฟล์setState.


4
คำว่า 'break condition' หมายถึงอะไร? ตรวจสอบว่าสถานะถูกตั้งค่าแล้วและไม่ได้รีเซ็ต?
Katerina Pavlenko

ฉันเห็นด้วยกับสิ่งนี้ความคิดเห็นเพิ่มเติมเพียงอย่างเดียวของฉันคือการเพิ่ม / ลบชั้นเรียนอาจไม่จำเป็นcomponentDidUpdateและสามารถเพิ่มได้ตามต้องการrenderแทน
Randy Morris

แต่การเพิ่ม / ลบคลาสขึ้นอยู่กับตำแหน่งแบบเลื่อนลงซึ่งตรวจสอบใน componentDidUpdate คุณแนะนำให้ตรวจสอบสองครั้งหรือไม่ และตามที่ฉันเข้าใจ componentDidUpdate เรียกว่า AFTER render () ดังนั้นจึงไม่มีประโยชน์ที่จะเพิ่ม / ลบคลาสในการเรนเดอร์ ()
Katerina Pavlenko

ฉันได้เพิ่มรหัสของฉันด้วย setstate คุณสามารถตรวจสอบและชี้ให้ฉันเห็นความผิดพลาดของฉันได้หรือไม่? หรือแสดงตัวอย่างที่จะไม่ทำให้เกิดการวนซ้ำ
Katerina Pavlenko

3
componentDidUpdate (prevProps, prevState) {if (prevState.x! == this.state.x) {// Do Something}}
Ashok R

70

ลายเซ็นcomponentDidUpdate void::componentDidUpdate(previousProps, previousState)ด้วยวิธีนี้คุณจะสามารถทดสอบได้ว่าอุปกรณ์ / สถานะใดสกปรกและโทรsetStateตามได้

ตัวอย่าง:

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

componentDidMountไม่มีข้อโต้แย้งใด ๆ และจะถูกเรียกเมื่อสร้างส่วนประกอบเท่านั้นดังนั้นจึงไม่สามารถใช้เพื่อวัตถุประสงค์ที่อธิบายไว้ได้
Jules

@Jules ขอบคุณ! ฉันเคยเขียนcomponentDidMountดังนั้นเมื่อฉันเขียนคำตอบชื่อดังเรียงซ้อน😮อีกครั้งขอบคุณและติดตามมาก!
Abdennour TOUMI

componentDidUpdate(prevProps, prevState) { if ( prevState.x!== this.state.x) { //Do Something } }
Ashok R

ฉันรู้ว่าคุณเป็นห่วง @AshokR คุณลดชื่ออาร์กิวเมนต์ แต่ "ก่อนหน้า" อาจหมายถึงป้องกันไม่ก่อนหน้า .. hhh .kidding :)
Abdennour TOUMI

59

หากคุณใช้setStateภายในcomponentDidUpdateจะอัปเดตคอมโพเนนต์ส่งผลให้มีการโทรcomponentDidUpdateที่เรียกsetStateอีกครั้งในภายหลังซึ่งส่งผลให้ลูปไม่มีที่สิ้นสุด คุณควรโทรตามเงื่อนไขsetStateและตรวจสอบให้แน่ใจว่าเงื่อนไขที่ละเมิดการโทรเกิดขึ้นในที่สุดเช่น:

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

ในกรณีที่คุณกำลังอัปเดตส่วนประกอบโดยส่งอุปกรณ์ประกอบฉากไปเท่านั้น (ไม่ได้รับการอัปเดตโดย setState ยกเว้นกรณีที่อยู่ใน componentDidUpdate) คุณสามารถเรียกsetStateภายในcomponentWillReceivePropsแทนcomponentDidUpdateได้


2
คำถามเก่า แต่ componentWillReceiveProps เลิกใช้แล้วและควรใช้ componentWillRecieveProps คุณไม่สามารถ setState ภายในวิธีนี้
Brooks DuBois

คุณหมายถึงgetDerivedStateFromProps.
adi518

5

ตัวอย่างนี้จะช่วยให้คุณเข้าใจการตอบสนอง Life Cycle เบ็ด

คุณสามารถsetStateในgetDerivedStateFromPropsวิธีการเช่นและเรียกวิธีการหลังจากที่มีการเปลี่ยนแปลงในอุปกรณ์ประกอบฉากstaticcomponentDidUpdate

ในcomponentDidUpdateคุณจะได้รับ3getSnapshotBeforeUpdateพระรามซึ่งผลตอบแทนจาก

คุณสามารถตรวจสอบโค้ดนี้ได้ที่ลิงค์แซนด์บ็อกซ์

// 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>


2

ฉันจะบอกว่าคุณต้องตรวจสอบว่าสถานะนั้นมีค่าเดียวกับที่คุณพยายามตั้งค่าหรือไม่ หากเหมือนกันจะไม่มีจุดที่ต้องตั้งค่าสถานะสำหรับค่าเดียวกันอีก

อย่าลืมตั้งค่าสถานะของคุณดังนี้:

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

-1

ฉันมีปัญหาคล้าย ๆ กันที่ฉันต้องจัดตำแหน่งเครื่องมือให้อยู่ตรงกลาง ตอบสนอง setState ใน componentDidUpdate ทำให้ฉันอยู่ในลูปที่ไม่มีที่สิ้นสุดฉันลองเงื่อนไขมันใช้งานได้ แต่ฉันพบว่าการใช้ในการเรียกกลับอ้างอิงทำให้ฉันมีวิธีแก้ปัญหาที่ง่ายและสะอาดกว่าหากคุณใช้ฟังก์ชันอินไลน์สำหรับการโทรกลับอ้างอิงคุณจะต้องเผชิญกับปัญหาว่างสำหรับการอัปเดตส่วนประกอบทุกครั้ง ดังนั้นใช้การอ้างอิงฟังก์ชันในการเรียกกลับอ้างอิงและตั้งค่าสถานะที่นั่นซึ่งจะเริ่มการแสดงผลอีกครั้ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.