programing

마운트 해제된 컴포넌트의 Respect - setState()

iphone6s 2023. 3. 28. 21:21
반응형

마운트 해제된 컴포넌트의 Respect - setState()

리액트 컴포넌트에서는 Ajax 요청이 진행 중일 때 단순한 스피너를 구현하려고 합니다.로드 상태를 저장하기 위해 상태를 사용하고 있습니다.

어떤 이유로 인해 내 React 컴포넌트에 있는 아래의 코드 조각이 이 오류를 발생시킵니다.

마운트 또는 마운트 구성 요소만 업데이트할 수 있습니다.이는 보통 마운트 해제된 컴포넌트에서 setState()를 호출했음을 의미합니다.이건 수술 금지야미정의 컴포넌트의 코드를 확인해 주세요.

첫 번째 setState 콜을 삭제하면 오류가 사라집니다.

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }

문제는 컴포넌트가 이미 마운트되어 있어야 하는데 (componentDidMount에서 호출되어) 컴포넌트가 마운트된 후 상태를 설정해도 안전하다고 생각했을 때 이 오류가 발생하는 이유는 무엇입니까?

렌더 기능이 보이지 않는 것은 조금 힘듭니다.는 이미 수행해야 할 작업을 검출할 수 있지만, 간격을 사용할 때마다 마운트 해제 시 해당 작업을 클리어해야 합니다.그래서:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

이러한 성공 및 오류 콜백은 마운트 해제 후에도 호출될 수 있으므로 interval 변수를 사용하여 마운트 여부를 확인할 수 있습니다.

this.loadInterval && this.setState({
    loading: false
});

이것이 도움이 되기를 바라며, 이것이 효과가 없으면 렌더링 기능을 제공하십시오.

건배.

문제는 컴포넌트가 이미 마운트되어 있어야 하는데 (componentDidMount에서 호출되어) 컴포넌트가 마운트된 후 상태를 설정해도 안전하다고 생각했을 때 이 오류가 발생하는 이유는 무엇입니까?

에서 호출되지 않았습니다.componentDidMount의 . . . . . . . .componentDidMount핸들러의 합니다.이 함수는, 「」의 .componentDidMount콜백(*)이 될 것 this.loadSearches컴포넌트가 마운트 해제되어 있는)이 실행됩니다.

그래서 받아들여진 답변이 당신을 보호할 것입니다.(일부 핸들러에 이미 제출된) 비동기 함수를 취소할 수 없는 다른 비동기 API를 사용하는 경우 다음을 수행할 수 있습니다.

if (this.isMounted())
     this.setState(...

모든 만, 하고 있는 ( 「」와 같이).그러나 이것은, 특히 당신의 API가 취소 기능을 제공하고 있는 경우(예:setInterval 사용하다clearInterval를 참조해 주세요.

다른 옵션이 필요한 경우 ref 속성의 콜백 방식을 회피책이 될 수 있습니다.handleRef의 파라미터는 div DOM 요소에 대한 참조입니다.

참조 및 DOM 의 상세한 것에 대하여는, https://facebook.github.io/react/docs/refs-and-the-dom.html 를 참조해 주세요.

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}
class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}

리액트 훅에 의해 활성화된 솔루션을 공유합니다.

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

fetch id 변경 시 이전 요청을 취소하고 싶을 때 까지 동일한 솔루션을 확장할 수 있습니다.그렇지 않으면 실행 중인 여러 요청 간에 레이스 조건이 발생할 수 있습니다(this.setState(불규칙으로 호출됨)

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

Javascript의 closes 덕분에 가능합니다.

일반적으로 위의 아이디어는 react doc가 권장하는 makeCancelable 접근법에 가까웠습니다.이 접근방식은 명확하게 기술되어 있습니다.

isMounted는 Antiattern입니다.

신용 거래

https://contangaramendy.dev/use-contraction-procription/

후세를 위해

이 경우 이 오류는 Reflux, 콜백, 리다이렉트 및 setState와 관련되어 있습니다.setState를 onDone 콜백으로 전송했지만 onSuccess 콜백으로 리다이렉트도 전송했습니다.성공한 경우 onSuccess 콜백은 onDone 전에 실행됩니다.로 인해 setState가 시행되기 전에 리다이렉트가 발생합니다.따라서 마운트 해제된 컴포넌트의 setState 오류입니다.

환류 저장소 작업:

generateWorkflow: function(
    workflowTemplate,
    trackingNumber,
    done,
    onSuccess,
    onFail)
{...

수정 전 문의:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    this.setLoading.bind(this, false),
    this.successRedirect
);

수정 후 호출:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    null,
    this.successRedirect,
    this.setLoading.bind(this, false)
);

React의 isMounted는 "비사용/반패턴"이기 때문에 _mounted 변수를 사용하여 직접 모니터링하는 경우도 있습니다.

참고로.CPromise를 데코레이터와 함께 사용하면 다음과 같은 트릭을 할 수 있습니다(라이브 데모는 이쪽).

export class TestComponent extends React.Component {
  state = {};

  @canceled(function (err) {
    console.warn(`Canceled: ${err}`);
    if (err.code !== E_REASON_DISPOSED) {
      this.setState({ text: err + "" });
    }
  })
  @listen
  @async
  *componentDidMount() {
    console.log("mounted");
    const json = yield this.fetchJSON(
      "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s"
    );
    this.setState({ text: JSON.stringify(json) });
  }

  @timeout(5000)
  @async
  *fetchJSON(url) {
    const response = yield cpFetch(url); // cancellable request
    return yield response.json();
  }

  render() {
    return (
      <div>
        AsyncComponent: <span>{this.state.text || "fetching..."}</span>
      </div>
    );
  }

  @cancel(E_REASON_DISPOSED)
  componentWillUnmount() {
    console.log("unmounted");
  }
}

언급URL : https://stackoverflow.com/questions/32903001/react-setstate-on-unmounted-component

반응형