Вызов setState для несмонтированного компонента - PullRequest
0 голосов
/ 06 июня 2018

Во многих моих компонентах мне нужно сделать что-то вроде этого:

handleSubmit() {
  this.setState({loading: true})
  someAsyncFunc()
    .then(() => {
      return this.props.onSuccess()
    })
    .finally(() => this.setState({loading: false}))
}

Функция onSuccess

  • может быть или не быть обещанием (если оноloading должен оставаться верным до тех пор, пока не будет решен)
  • может отключить или не отключить компонент (он может закрыть модальный режим, в котором находится этот компонент, или даже перейти на другую страницу)

Если функция отключает компонент, this.setState({loading: false}) явно вызывает предупреждение Can't call setState (or forceUpdate) on an unmounted component.

Мои 2 вопроса:

  1. Существует ли простой способ избежать этой проблемы?Я не хочу устанавливать переменные _isMounted в componentDidMount и componentWillUnmount, а затем проверять их при необходимости в большинстве моих компонентов, плюс я могу забыть сделать это в следующий раз, написав что-то вроде этого ...
  2. Это действительно проблема?Я знаю, что, согласно предупреждению, it indicates a memory leak in my application, но это не утечка памяти в этом случае, не так ли?Может быть, игнорирование предупреждения будет в порядке ...

РЕДАКТИРОВАТЬ: Второй вопрос немного важнее для меня, чем первый.Если это действительно проблема, и я просто не могу вызвать setState для неустановленного компонента, я, вероятно, сам найду обходной путь.Но мне любопытно, если я не могу просто проигнорировать это.

Живой пример проблемы:

const someAsyncFunc = () => new Promise(resolve => {
  setTimeout(() => {
  	console.log("someAsyncFunc resolving");
    resolve("done");
  }, 2000);
});

class Example extends React.Component {
  constructor(...args) {
    super(...args);
    this.state = {loading: false};
  }
  
  componentDidMount() {
    setTimeout(() => this.handleSubmit(), 100);
  }
  
  handleSubmit() {
    this.setState({loading: true})
    someAsyncFunc()
      /*
      .then(() => {
        return this.props.onSuccess()
      })
      */
      .finally(() => this.setState({loading: false}))
  }
  
  render() {
    return <div>{String(this.state.loading)}</div>;
  }
}

class Wrapper extends React.Component {
	constructor(props, ...rest) {
  	super(props, ...rest);
    this.state = {
    	children: props.children
    };
  }
	componentDidMount() {
  	setTimeout(() => {
    	console.log("removing");
    	this.setState({children: []});
    }, 1500)
  }
	render() {
  	return <div>{this.state.children}</div>;
  }
}

ReactDOM.render(
	<Wrapper>
    <Example />
	</Wrapper>,
  document.getElementById("root")
);
.as-console-wrapper {
  max-height: 100% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>

Ответы [ 4 ]

0 голосов
/ 18 января 2019
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('example.com').then(data => {
      if (this._isMounted) {
        this.setState({ data })
      }
    });
  }


  render() {
    ...
  }
}
0 голосов
/ 06 июня 2018

Вы должны быть в состоянии использовать this._isMounted, чтобы проверить, смонтирован ли компонент на самом деле.

handleSubmit() {
  this.setState({loading: true})
  someAsyncFunc()
    .then(() => {
      return this.props.onSuccess()
    })
    .finally(() => {
      if (this && this._isMounted) { // check if component is still mounted
        this.setState({loading: false})
      }
    })
}

Но имейте в виду, что этот подход считается антипаттерном.https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

0 голосов
/ 07 сентября 2018

А как же

componentWillUnmount() {  
    // Assign this.setState to empty function to avoid console warning
    // when this.setState is called on an unmounted component
    this.setState = () => undefined;
}
0 голосов
/ 06 июня 2018

К сожалению, вы должны отслеживать «isMounting» самостоятельно.Чтобы упростить управление потоком, вы можете использовать async/await:

handleSubmit() {
  this.setState({loading: true})
  try {
    await someAsyncFunction()
    await this.props.onSuccess()
  } finally {
    if (this._isMounted) {
      this.setState({loading: false})
    }
  }
}

Это на самом деле упоминается в реагирующих документах , что указывает на это решение: https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6

Если ваш someAsyncFunction поддерживает отмену, вы должны сделать это в componentWillUnmount , как рекомендовано этой статьей .Но тогда - конечно же - проверьте возвращаемое значение и в итоге не вызовите this.props.onSuccess.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...