Igo*_*nko 28 javascript setstate typescript lodash reactjs
我正在React中编写一个应用程序,无法避免出现一个超级常见的陷阱,该陷阱正在调用setState(...)
after componentWillUnmount(...)
。
我非常仔细地查看了我的代码,并尝试放置一些保护子句,但是问题仍然存在,并且我仍在观察警告。
因此,我有两个问题:
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount
method.
in TextLayerInternal (created by Context.Consumer)
in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29
Run Code Online (Sandbox Code Playgroud)
Book.tsx
import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: () => void;
pdfWrapper: HTMLDivElement | null = null;
isComponentMounted: boolean = false;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
constructor(props: any) {
super(props);
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
);
}
componentDidMount = () => {
this.isComponentMounted = true;
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
this.isComponentMounted = false;
window.removeEventListener("resize", this.setDivSizeThrottleable);
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
Run Code Online (Sandbox Code Playgroud)
AutoWidthPdf.tsx
import * as React from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
interface IProps {
file: string;
width: number;
onLoadSuccess: (pdf: any) => void;
}
export class AutoWidthPdf extends React.Component<IProps> {
render = () => (
<Document
file={this.props.file}
onLoadSuccess={(_: any) => this.props.onLoadSuccess(_)}
>
<Page
pageNumber={1}
width={this.props.width}
/>
</Document>
);
}
Run Code Online (Sandbox Code Playgroud)
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: ((() => void) & Cancelable) | undefined;
pdfWrapper: HTMLDivElement | null = null;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
componentDidMount = () => {
this.setDivSizeThrottleable = throttle(
() => {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
},
500,
);
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.setDivSizeThrottleable!);
this.setDivSizeThrottleable!.cancel();
this.setDivSizeThrottleable = undefined;
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable!();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
Run Code Online (Sandbox Code Playgroud)
for*_*d04 264
这是一个React Hooks特定的解决方案
警告:无法对卸载的组件执行 React 状态更新。
您可以声明let isMounted = true
inside useEffect
,一旦卸载组件,它将在清理回调中更改。在状态更新之前,您现在有条件地检查此变量:
useEffect(() => {
let isMounted = true; // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []); // adjust dependencies to your needs
Run Code Online (Sandbox Code Playgroud)
useEffect(() => {
let isMounted = true; // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []); // adjust dependencies to your needs
Run Code Online (Sandbox Code Playgroud)
const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};
const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useEffect(() => {
let isMounted = true;
fetchData();
return () => {
isMounted = false;
};
// simulate some Web API fetching
function fetchData() {
setTimeout(() => {
// drop "if (isMounted)" to trigger error again
// (take IDE, doesn't work with stack snippet)
if (isMounted) setState("data fetched")
else console.log("aborted setState on unmounted component")
}, 4000);
}
}, []);
return <div>Child: {state}</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
Run Code Online (Sandbox Code Playgroud)
useAsync
挂钩我们可以将所有样板封装到一个自定义 Hook 中,如果组件卸载或依赖值之前发生更改,它会自动中止异步函数:
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data);
});
return () => { isActive = false };
}, [asyncFn, onSuccess]);
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>
Run Code Online (Sandbox Code Playgroud)
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data);
});
return () => { isActive = false };
}, [asyncFn, onSuccess]);
}
Run Code Online (Sandbox Code Playgroud)
有关效果清理的更多信息:反应过度:useEffect 完整指南
May*_*bit 70
如果上述解决方案不起作用,试试这个,它对我有用:
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}
Run Code Online (Sandbox Code Playgroud)
sfl*_*che 26
有一个很常见的钩子可以useIsMounted
解决这个问题(对于功能组件)......
import { useRef, useEffect } from 'react';
export function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
}
Run Code Online (Sandbox Code Playgroud)
然后在您的功能组件中
function Book() {
const isMounted = useIsMounted();
...
useEffect(() => {
asyncOperation().then(data => {
if (isMounted.current) { setState(data); }
})
});
...
}
Run Code Online (Sandbox Code Playgroud)
小智 22
根据 React 文档,检查组件是否已安装实际上是一种反模式。警告的解决方案setState
是利用以下内容AbortController
:
useEffect(() => {
const abortController = new AbortController() // creating an AbortController
fetch(url, { signal: abortController.signal }) // passing the signal to the query
.then(data => {
setState(data) // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return // if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort() // stop the query by aborting on the AbortController on unmount
}
}, [])
Run Code Online (Sandbox Code Playgroud)
对于不基于 Fetch API 的异步操作,仍然应该有一种方法来取消这些异步操作,并且您应该利用这些操作,而不仅仅是检查组件是否已安装。如果您正在构建自己的 API,则可以在其中实现 AbortController API 来处理它。
对于更多上下文,检查组件是否已安装是一种反模式,因为React 会在内部检查组件是否已安装以显示该警告。再次进行相同的检查只是隐藏警告的一种方法,并且有一些比在代码库的很大一部分上添加这段代码更简单的方法来隐藏它们。
来源:https ://medium.com/doctolib/react-stop-checking-if-your-component-is-mounted-3bb2568a4934
vin*_*nod 15
To remove - Can't perform a React state update on an unmounted component warning, use componentDidMount method under a condition and make false that condition on componentWillUnmount method. For example : -
class Home extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
this._isMounted = true;
ajaxVar
.get('https://domain')
.then(result => {
if (this._isMounted) {
this.setState({
news: result.data.hits,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}
Run Code Online (Sandbox Code Playgroud)
scr*_*2em 10
React 已经删除了这个警告,但这里有一个更好的解决方案(不仅仅是解决方法)
useEffect(() => {
const abortController = new AbortController() // creating an AbortController
fetch(url, { signal: abortController.signal }) // passing the signal to the query
.then(data => {
setState(data) // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return // if the query has been aborted, do nothing
throw error
})
return () => {
abortController.abort()
}
}, [])
Run Code Online (Sandbox Code Playgroud)
@ford04 的解决方案对我来说不起作用,特别是如果您需要在多个地方使用 isMounted (例如多个 useEffect ),建议使用 useRef ,如下所示:
"dependencies":
{
"react": "17.0.1",
}
"devDependencies": {
"typescript": "4.1.5",
}
Run Code Online (Sandbox Code Playgroud)
export const SubscriptionsView: React.FC = () => {
const [data, setData] = useState<Subscription[]>();
const isMounted = React.useRef(true);
React.useEffect(() => {
if (isMounted.current) {
// fetch data
// setData (fetch result)
return () => {
isMounted.current = false;
};
}
}
});
Run Code Online (Sandbox Code Playgroud)
尝试更改setDivSizeThrottleable
为
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
{ leading: false, trailing: true }
);
Run Code Online (Sandbox Code Playgroud)
小智 5
我知道您没有使用历史记录,但在我的情况下,我使用了useHistory
React Router DOM 中的钩子,它在状态保留在我的 React Context Provider 中之前卸载了组件。
为了解决这个问题,我使用了withRouter
嵌套组件的钩子,在我的例子中export default withRouter(Login)
,在组件内部const Login = props => { ...; props.history.push("/dashboard"); ...
。我还props.history.push
从组件中删除了另一个,例如,if(authorization.token) return props.history.push('/dashboard')
因为这会导致循环,因为authorization
状态。
将新项目推送到history的替代方法。
向 jsx 组件添加引用,然后检查它是否存在
function Book() {
const ref = useRef();
useEffect(() => {
asyncOperation().then(data => {
if (ref.current) setState(data);
})
});
return <div ref={ref}>content</div>
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
32188 次 |
最近记录: |