setState () bên trong thành phầnDidUpdate ()


131

Tôi đang viết một tập lệnh di chuyển thả xuống bên dưới hoặc bên trên đầu vào tùy thuộc vào chiều cao của danh sách thả xuống và vị trí của đầu vào trên màn hình. Ngoài ra tôi muốn đặt modifier thành dropdown theo hướng của nó. Nhưng sử dụng setStatebên trong componentDidUpdatetạo ra một vòng lặp vô hạn (đó là điều hiển nhiên)

Tôi đã tìm thấy một giải pháp trong việc sử dụng getDOMNodevà đặt trực tiếp tên lớp xuống, nhưng tôi cảm thấy rằng nên có một giải pháp tốt hơn bằng các công cụ React. Ai có thể giúp tôi?

Đây là một phần của mã làm việc với getDOMNode(logic định vị bị bỏ qua một chút để đơn giản hóa mã)

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

và đây là mã với setstate (tạo ra một vòng lặp vô hạn)

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
Tôi nghĩ mẹo ở đây là setStatesẽ luôn kích hoạt kết xuất lại. Thay vì kiểm tra state.topvà gọi setStatenhiều lần, chỉ cần theo dõi những gì bạn muốn state.topở một biến cục bộ, sau đó một lần vào cuối componentDidUpdatecuộc gọi setStatechỉ khi biến cục bộ của bạn không khớp state.top. Vì nó đứng ngay bây giờ, bạn ngay lập tức thiết lập lại state.topsau lần kết xuất lại đầu tiên, điều này đặt bạn vào vòng lặp vô hạn.
Randy Morris

2
Xem hai triển khai khác nhau componentDidUpdatetrong fiddle này .
Randy Morris

Chết tiệt! biến cục bộ giải quyết toàn bộ vấn đề, làm thế nào tôi không tìm ra nó bằng mysef! Cảm ơn bạn!
Katerina Pavlenko

1
Tôi nghĩ bạn nên chấp nhận câu trả lời dưới đây. Nếu bạn đọc lại tôi nghĩ bạn sẽ thấy nó trả lời đủ câu hỏi ban đầu.
Randy Morris

Tại sao không có ai đề nghị chuyển điều kiện vào componentShouldUpdate?
Patrick Roberts

Câu trả lời:


116

Bạn có thể sử dụng setStatebên trong componentDidUpdate. Vấn đề là bằng cách nào đó bạn đang tạo ra một vòng lặp vô hạn vì không có điều kiện phá vỡ.

Dựa trên thực tế là bạn cần các giá trị được cung cấp bởi trình duyệt sau khi thành phần được hiển thị, tôi nghĩ cách tiếp cận của bạn về việc sử dụng componentDidUpdatelà chính xác, nó chỉ cần xử lý tốt hơn các điều kiện kích hoạt setState.


4
"Điều kiện phá vỡ" nghĩa là gì? kiểm tra nếu trạng thái đã được thiết lập và không đặt lại nó?
Katerina Pavlenko

Tôi đồng ý với điều này, nhận xét bổ sung duy nhất của tôi sẽ là việc thêm / xóa các lớp có lẽ không cần thiết componentDidUpdatevà chỉ có thể được thêm vào khi cần thiết render.
Randy Morris

nhưng việc thêm / xóa lớp phụ thuộc vào vị trí thả xuống được kiểm tra trong thành phầnDidUpdate, bạn đề nghị kiểm tra nó hai lần? Và theo tôi hiểu, thành phầnDidUpdate được gọi là AFTER render (), do đó, việc thêm / xóa lớp trong kết xuất ()
Katerina Pavlenko

tôi đã thêm mã của mình với setstate, bạn có thể kiểm tra nó và chỉ cho tôi lỗi không? hoặc chỉ cho tôi một số ví dụ sẽ không gây ra vòng lặp
Katerina Pavlenko

2
componentDidUpdate (trướcProps, trướcState) {if (trướcState.x! == this.state.x) {// Làm gì đó}}
Ashok R

68

Các componentDidUpdatechữ ký là void::componentDidUpdate(previousProps, previousState). Với điều này, bạn sẽ có thể kiểm tra đạo cụ / trạng thái nào bị bẩn và gọi setStatephù hợp.

Thí dụ:

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

componentDidMountkhông có bất kỳ đối số nào và chỉ được gọi khi thành phần được tạo, vì vậy không thể được sử dụng cho mục đích được mô tả.
Jules

@Jules Cảm ơn! Tôi đã từng viết componentDidMount, vì vậy khi tôi viết câu trả lời, cái tên nổi tiếng xếp tầng 😮 Một lần nữa, Cảm ơn & Tuyệt vời bắt kịp!
Abdennour TOUMI

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

Tôi biết mối quan tâm của bạn @AshokR. Bạn giảm tên arg. nhưng "trước" có thể có nghĩa là ngăn chặn không trước đó .. hhh. .kidding :)
Abdennour TOUMI

58

Nếu bạn sử dụng setStatebên trong componentDidUpdatenó sẽ cập nhật thành phần, dẫn đến một cuộc gọi componentDidUpdatemà sau đó gọi setStatelại dẫn đến vòng lặp vô hạn. Bạn nên gọi có điều kiện setStatevà đảm bảo rằng tình trạng vi phạm cuộc gọi xảy ra cuối cùng, vd:

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

Trong trường hợp bạn chỉ cập nhật thành phần bằng cách gửi đạo cụ đến nó (nó không được setState cập nhật, ngoại trừ trường hợp bên trong thành phầnDidUpdate), bạn có thể gọi setStatebên trong componentWillReceivePropsthay vì componentDidUpdate.


2
câu hỏi cũ nhưng thành phầnWillReceiveProps không được dùng nữa và thành phầnWillRecieveProps nên được sử dụng. Bạn không thể setState bên trong phương thức này.
Brooks DuBois

Bạn có ý nghĩa getDerivedStateFromProps.
adi518

5

Ví dụ này sẽ giúp bạn hiểu về Móc vòng đời React .

Bạn có thể setStatetrong getDerivedStateFromPropsphương thức tức là staticvà kích hoạt phương thức sau khi đạo cụ thay đổi componentDidUpdate.

Trong componentDidUpdatebạn sẽ nhận được thông số thứ 3 mà trả về getSnapshotBeforeUpdate.

Bạn có thể kiểm tra liên kết mã và hộp này

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

Tôi sẽ nói rằng bạn cần kiểm tra xem trạng thái đã có cùng giá trị mà bạn đang cố gắng đặt chưa. Nếu nó giống nhau, không có điểm nào để đặt lại trạng thái cho cùng một giá trị.

Đảm bảo đặt trạng thái của bạn như thế này:

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

-1

Tôi đã có một vấn đề tương tự khi tôi phải tập trung vào toolTip. React setState trong componentDidUpdate đã đưa tôi vào vòng lặp vô hạn, tôi đã thử điều kiện nó hoạt động. Nhưng tôi thấy việc sử dụng trong gọi lại ref đã cho tôi giải pháp đơn giản và gọn gàng hơn, nếu bạn sử dụng chức năng nội tuyến cho gọi lại ref, bạn sẽ phải đối mặt với vấn đề null cho mỗi bản cập nhật thành phần. Vì vậy, sử dụng tham chiếu hàm trong gọi lại ref và đặt trạng thái ở đó, sẽ bắt đầu kết xuất lại

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.