Думаю, название говорит само за себя. Желтое предупреждение отображается каждый раз, когда я отключаю компонент, который все еще загружается.
ConsoleWarning: Can't call
setState(orforceUpdate) on an unmounted component. This is a no-op, but ... To fix, cancel all subscriptions and asynchronous tasks in thecomponentWillUnmountmethod.
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
return fetch('LINK HERE')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
вопрос обновлен
вы обещали или асинхронный код для выборки
добавить код получения в запрос
см. isMounted - это антипаттерн и прерывание выборки.





Когда вы запускаете обещание, может пройти несколько секунд, прежде чем оно разрешится, и к этому времени пользователь может перейти в другое место в вашем приложении. Поэтому, когда Promise разрешает, setState выполняется на отключенном компоненте, и вы получаете сообщение об ошибке - как и в вашем случае. Это также может вызвать утечку памяти.
Вот почему лучше всего вынести часть асинхронной логики из компонентов.
В противном случае вам нужно будет как-то отмени свое обещание. В качестве альтернативы - в крайнем случае (это антипаттерн) - вы можете сохранить переменную, чтобы проверять, смонтирован ли компонент:
componentDidMount(){
this.mounted = true;
this.props.fetchData().then((response) => {
if (this.mounted) {
this.setState({ data: response })
}
})
}
componentWillUnmount(){
this.mounted = false;
}
Подчеркну еще раз - этот это антипаттерн, но может быть достаточным в вашем случае (как и в случае с реализацией Formik).
Аналогичное обсуждение на GitHub
Обновлено:
Вероятно, вот как я могу решить ту же проблему (не имея ничего, кроме React) с Хуки:
ВАРИАНТ А:
import React, { useState, useEffect } from "react";
export default function Page() {
const value = usePromise("https://something.com/api/");
return (
<p>{value ? value : "fetching data..."}</p>
);
}
function usePromise(url) {
const [value, setState] = useState(null);
useEffect(() => {
let isMounted = true; // track whether component is mounted
request.get(url)
.then(result => {
if (isMounted) {
setState(result);
}
});
return () => {
// clean up
isMounted = false;
};
}, []); // only on "didMount"
return value;
}
ВАРИАНТ Б: В качестве альтернативы useRef, который ведет себя как статическое свойство класса, что означает, что он не выполняет повторную визуализацию компонента при изменении его значения:
function usePromise2(url) {
const isMounted = React.useRef(true)
const [value, setState] = useState(null);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
request.get(url)
.then(result => {
if (isMounted.current) {
setState(result);
}
});
}, []);
return value;
}
// or extract it to custom hook:
function useIsMounted() {
const isMounted = React.useRef(true)
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted; // returning "isMounted.current" wouldn't work because we would return unmutable primitive
}
Пример: https://codesandbox.io/s/86n1wq2z8
так что нет реального способа просто отменить выборку для componentWillUnmount?
@ JoãoBelo Я не уверен, поддерживается ли это. Взгляните в этом пакете
О, я раньше не замечал код вашего ответа, он сработал. Благодарность
см. isMounted - это антипаттерн и прерывание выборки.
Теперь необходимо присвоить переменной isMounted другое имя, иначе вы получите ошибку setting getter-only property .
что вы имеете в виду, говоря «Вот почему асинхронную логику лучше всего вынести из компонентов»? Разве все в React не является компонентом?
@Karpik, я имею в виду использование redux или mobx или другую библиотеку управления состоянием. Однако новые функции, такие как задержка реакции, могут решить эту проблему.
@Tomasz Mularczyk Большое спасибо, вы сделали достойный материал.
Также смотрите FAQ по React Hooks для пример.
Использование управляющей переменной, такой как this.mounted, которую вы использовали, - это именно то, что я думал в качестве первого решения. Я на самом деле ищу другие решения, но, поскольку вы упомянули об этом в первый раз, я думаю, что буду использовать его. Спасибо за косвенное подтверждение той же идеи, что и моя.
Решение React Hooks лучше, чем первое решение? Он использует новую причудливую функцию, но в основном работает так же.
Предупреждение: isMounted (...) устарел в простых классах JavaScript React.
помимо того, что это анти-шаблон, первое решение не работает, потому что setState является АСИНХРОННЫМ
Внимательно прочтите ссылку на антишаблон. Использование метода isMounted() является анти-шаблоном, но рекомендуется использовать свойство, подобное _isMounted.
Когда мне нужно «отменить все подписки и асинхронно», я обычно отправляю что-то в redux в componentWillUnmount, чтобы проинформировать всех других подписчиков и при необходимости отправить еще один запрос об отмене на сервер.
Думаю, я придумал способ обойти это. Проблема не столько в самой выборке, сколько в setState после закрытия компонента. Итак, решение заключалось в том, чтобы установить this.state.isMounted как false, а затем на componentWillMount изменить его на true, а в componentWillUnmount снова установить на false. Затем просто if (this.state.isMounted) setState внутри выборки. Вот так:
constructor(props){
super(props);
this.state = {
isMounted: false,
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
this.setState({
isMounted: true,
})
return fetch('LINK HERE')
.then((response) => response.json())
.then((responseJson) => {
if (this.state.isMounted){
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
}
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount() {
this.setState({
isMounted: false,
})
}
setState, вероятно, не идеален, поскольку он не обновляет значение в состоянии немедленно.
Суть этого предупреждения в том, что у вашего компонента есть ссылка на него, удерживаемая каким-то невыполненным обратным вызовом / обещанием.
Чтобы избежать антипаттерна сохранения вашего состояния isMounted (которое поддерживает работу вашего компонента), как это было сделано во втором шаблоне, сайт реакции предлагает используя необязательное обещание; однако этот код, кажется, также поддерживает ваш объект в живых.
Вместо этого я сделал это, используя закрытие с вложенной функцией привязки к setState.
Вот мой конструктор (машинописный текст)…
constructor(props: any, context?: any) {
super(props, context);
let cancellable = {
// it's important that this is one level down, so we can drop the
// reference to the entire object by setting it to undefined.
setState: this.setState.bind(this)
};
this.componentDidMount = async () => {
let result = await fetch(…);
// ideally we'd like optional chaining
// cancellable.setState?.({ url: result || '' });
cancellable.setState && cancellable.setState({ url: result || '' });
}
this.componentWillUnmount = () => {
cancellable.setState = undefined; // drop all references.
}
}
Это концептуально ничем не отличается от сохранения флага isMounted, только вы привязываете его к закрытию, а не навешиваете его на this.
Дружелюбные люди из React рекомендую оборачивают ваши вызовы / обещания fetch в отменяемые обещания. Хотя в этой документации нет рекомендаций по хранению кода отдельно от класса или функции с выборкой, это кажется целесообразным, потому что другие классы и функции могут нуждаться в этой функциональности, дублирование кода является анти-шаблоном и независимо от устаревшего кода. следует утилизировать или аннулировать в componentWillUnmount(). Согласно React, вы можете вызвать cancel() для обернутого обещания в componentWillUnmount, чтобы избежать установки состояния на размонтированном компоненте.
Предоставленный код будет выглядеть примерно так, если мы воспользуемся React в качестве руководства:
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
const cancelablePromise = makeCancelable(fetch('LINK HERE'));
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
cancelablePromise.
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, () => {
});
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount() {
cancelablePromise.cancel();
}
---- РЕДАКТИРОВАТЬ ----
Я обнаружил, что данный ответ может быть не совсем правильным, следя за проблемой на GitHub. Вот одна из версий, которые я использую, и она подходит для моих целей:
export const makeCancelableFunction = (fn) => {
let hasCanceled = false;
return {
promise: (val) => new Promise((resolve, reject) => {
if (hasCanceled) {
fn = null;
} else {
fn(val);
resolve(val);
}
}),
cancel() {
hasCanceled = true;
}
};
};
Идея заключалась в том, чтобы помочь сборщику мусора освободить память, сделав функцию или что-то еще, что вы используете, null.
у вас есть ссылка на проблему на github
@Ren, есть GitHub сайт для редактирования страницы и обсуждения вопросов.
Я больше не уверен, в чем именно заключается проблема в этом проекте GitHub.
Ссылка на выпуск GitHub: github.com/facebook/react/issues/5465
Вы можете использовать AbortController для отмены запроса на выборку.
См. Также: https://www.npmjs.com/package/abortcontroller-polyfill
class FetchComponent extends React.Component{
state = { todos: [] };
controller = new AbortController();
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => this.setState({ todos }))
.catch(e => alert(e.message));
}
componentWillUnmount(){
this.controller.abort();
}
render(){
return null;
}
}
class App extends React.Component{
state = { fetch: true };
componentDidMount(){
this.setState({ fetch: false });
}
render(){
return this.state.fetch && <FetchComponent/>
}
}
ReactDOM.render(<App/>, document.getElementById('root'))<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id = "root"></div>Хотел бы я знать, что существует веб-API для отмены запросов, таких как AbortController. Но ладно, еще не поздно это узнать. Спасибо.
Итак, если у вас несколько fetch, можете ли вы передать один AbortController всем им?
Поскольку сообщение было открыто, добавлена возможность прерывания. https://developers.google.com/web/updates/2017/09/abortable-fetch
(из документов :)
Контроллер + сигнальный маневр Встречайте AbortController и AbortSignal:
const controller = new AbortController();
const signal = controller.signal;
У контроллера есть только один метод:
controller.abort (); Когда вы это сделаете, он уведомит сигнал:
signal.addEventListener('abort', () => {
// Logs true:
console.info(signal.aborted);
});
Этот API предоставляется стандартом DOM, и это весь API. Он намеренно универсален, поэтому может использоваться другими веб-стандартами и библиотеками JavaScript.
например, вот как можно установить тайм-аут выборки через 5 секунд:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.info(text);
});
Интересно, попробую так. Но перед этим я сначала прочту AbortController API.
Можем ли мы использовать только один экземпляр AbortController для нескольких выборок, чтобы, когда мы вызываем метод прерывания этого единственного AbortController в компонентеWillUnmount, он отменял все существующие выборки в нашем компоненте? Если нет, это означает, что мы должны предоставить разные экземпляры AbortController для каждой выборки, верно?
@LexSoft вы нашли ответ на свой вопрос?
@Superdude ответ - да
Я думаю, что если нет необходимости сообщать серверу об отмене - лучший подход - просто использовать синтаксис async / await (если он доступен).
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
async componentDidMount() {
try {
const responseJson = await fetch('LINK HERE')
.then((response) => response.json());
this.setState({
isLoading: false,
dataSource: responseJson,
}
} catch {
console.error(error);
}
}
В дополнение к примерам перехватчиков отменяемых обещаний в принятом решении может быть удобно иметь ловушку useAsyncCallback, которая обертывает обратный вызов запроса и возвращает отменяемое обещание. Идея та же, но с крючком, работающим как обычный useCallback. Вот пример реализации:
function useAsyncCallback<T, U extends (...args: any[]) => Promise<T>>(callback: U, dependencies: any[]) {
const isMounted = useRef(true)
useEffect(() => {
return () => {
isMounted.current = false
}
}, [])
const cb = useCallback(callback, dependencies)
const cancellableCallback = useCallback(
(...args: any[]) =>
new Promise<T>((resolve, reject) => {
cb(...args).then(
value => (isMounted.current ? resolve(value) : reject({ isCanceled: true })),
error => (isMounted.current ? reject(error) : reject({ isCanceled: true }))
)
}),
[cb]
)
return cancellableCallback
}
Используя пакет CPromise, вы можете отменить цепочки обещаний, в том числе вложенные. Он поддерживает AbortController и генераторы в качестве замены асинхронных функций ECMA. Используя декораторы CPromise, вы можете легко управлять своими асинхронными задачами, делая их отменяемыми.
Использование декораторов Живая демонстрация:
import React from "react";
import { ReactComponent, timeout } from "c-promise2";
import cpFetch from "cp-fetch";
@ReactComponent
class TestComponent extends React.Component {
state = {
text: "fetching..."
};
@timeout(5000)
*componentDidMount() {
console.info("mounted");
const response = yield cpFetch(this.props.url);
this.setState({ text: `json: ${yield response.text()}` });
}
render() {
return <div>{this.state.text}</div>;
}
componentWillUnmount() {
console.info("unmounted");
}
}
Все этапы полностью отменяются / отменяются. Вот пример использования его с React Живая демонстрация
import React, { Component } from "react";
import {
CPromise,
CanceledError,
ReactComponent,
E_REASON_UNMOUNTED,
listen,
cancel
} from "c-promise2";
import cpAxios from "cp-axios";
@ReactComponent
class TestComponent extends Component {
state = {
text: ""
};
*componentDidMount(scope) {
console.info("mount");
scope.onCancel((err) => console.info(`Cancel: ${err}`));
yield CPromise.delay(3000);
}
@listen
*fetch() {
this.setState({ text: "fetching..." });
try {
const response = yield cpAxios(this.props.url).timeout(
this.props.timeout
);
this.setState({ text: JSON.stringify(response.data, null, 2) });
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED);
this.setState({ text: err.toString() });
}
}
*componentWillUnmount() {
console.info("unmount");
}
render() {
return (
<div className = "component">
<div className = "caption">useAsyncEffect demo:</div>
<div>{this.state.text}</div>
<button
className = "btn btn-success"
type = "submit"
onClick = {() => this.fetch(Math.round(Math.random() * 200))}
>
Fetch random character info
</button>
<button
className = "btn btn-warning"
onClick = {() => cancel.call(this, "oops!")}
>
Cancel request
</button>
</div>
);
}
}
Использование хуков и метода cancel
import React, { useState } from "react";
import {
useAsyncEffect,
E_REASON_UNMOUNTED,
CanceledError
} from "use-async-effect2";
import cpAxios from "cp-axios";
export default function TestComponent(props) {
const [text, setText] = useState("");
const [id, setId] = useState(1);
const cancel = useAsyncEffect(
function* () {
setText("fetching...");
try {
const response = yield cpAxios(
`https://rickandmortyapi.com/api/character/${id}`
).timeout(props.timeout);
setText(JSON.stringify(response.data, null, 2));
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED);
setText(err.toString());
}
},
[id]
);
return (
<div className = "component">
<div className = "caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button
className = "btn btn-success"
type = "submit"
onClick = {() => setId(Math.round(Math.random() * 200))}
>
Fetch random character info
</button>
<button className = "btn btn-warning" onClick = {cancel}>
Cancel request
</button>
</div>
);
}
еще один альтернативный способ - обернуть вашу асинхронную функцию в оболочку, которая будет обрабатывать вариант использования, когда компонент размонтируется
поскольку мы знаем, что функция также является объектом в js, поэтому мы можем использовать их для обновления значений закрытия
const promesifiedFunction1 = (func) => {
return function promesify(...agrs){
let cancel = false;
promesify.abort = ()=>{
cancel = true;
}
return new Promise((resolve, reject)=>{
function callback(error, value){
if (cancel){
reject({cancel:true})
}
error ? reject(error) : resolve(value);
}
agrs.push(callback);
func.apply(this,agrs)
})
}
}
//here param func pass as callback should return a promise object
//example fetch browser API
//const fetchWithAbort = promesifiedFunction2(fetch)
//use it as fetchWithAbort('http://example.com/movies.json',{...options})
//later in componentWillUnmount fetchWithAbort.abort()
const promesifiedFunction2 = (func)=>{
return async function promesify(...agrs){
let cancel = false;
promesify.abort = ()=>{
cancel = true;
}
try {
const fulfilledValue = await func.apply(this,agrs);
if (cancel){
throw 'component un mounted'
}else{
return fulfilledValue;
}
}
catch (rejectedValue) {
return rejectedValue
}
}
}
тогда внутри componentWillUnmount () просто вызовите promesifiedFunction.abort () это обновит флаг отмены и запустит функцию отклонения
что это предупреждение, у меня нет этой проблемы