반응 useEffect 원인:마운트 해제된 구성 요소에서 반응 상태 업데이트를 수행할 수 없음
데이터를 가져올 때 얻는 정보:마운트 해제된 구성 요소에서 반응 상태 업데이트를 수행할 수 없습니다.앱은 아직 작동하지만, 반응으로 인해 메모리 누수가 발생하고 있는 것 같습니다.
이것은 no-op이지만, 애플리케이션의 메모리 누수를 나타내고 있습니다.수정하려면 useEffect 정리 함수의 모든 구독 및 비동기 작업을 취소하십시오."
왜 자꾸 이 경고가 뜨죠?
저는 다음 솔루션을 조사하려고 했습니다.
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
하지만 이건 여전히 나에게 경고를 주고 있었다.
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.log(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
편집:
에 api api를 했습니다.AbortController()및 을 사용하였습니다.signal요청을 취소할 수 있습니다.
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
★★★spotify.getArtistProfile() 모양입니다.
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
, 내 에서 Promise.all 수 없다abort()그 약속 때문에 항상 국가를 위해 노력하겠습니다.
저는 도움이 되는 컴포넌트 마운트 해제 시 상태를 청소합니다.
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
의 공유AbortControllerfetch()적절한 접근법입니다.
다음 중 하나라도Promise중단되고, 는 중단됩니다.Promise.all()합니다.AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>
예를 들어 일부 비동기 작업을 수행한 다음 결과를 상태에 기록하고 페이지에 상태 내용을 표시하는 구성 요소가 있다고 가정합니다.
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
사용자가 해 보겠습니다.doVeryLongRequest()는 계속 실행됩니다. MyComponent마운트 해제되었지만 요청은 아직 활성화되어 있으며, 응답이 수신되면 (1) 행과 (2) 행에서 상태를 설정하고 HTML에서 적절한 노드를 변경하려고 합니다. 제목에서 오류가 발생합니다.
컴포넌트가 아직 탑재되어 있는지 여부를 확인함으로써 수정할 수 있습니다. 이번에는 '만들다'를 볼까요componentMounted참조(아래 행(3)) 및 설정true is is is음음음음음음음음음음 it it it it로 설정합니다.false(아래 (4)행).그리고 체크해 봅시다componentMounted(아래 줄 (5))을 설정할 때마다 변수를 지정합니다.
수정 코드:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
const componentMounted = useRef(true); // (3) component is mounted
// ...
useEffect( () => {
(async () => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it takes some time
// When request is finished:
if (componentMounted.current){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted.current = false; // (4) set it to false when we leave the page
}
})();
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<Link to="SOME_LOCAL_LINK">Go away from here!</Link>
</div>
);
}
왜 자꾸 이 경고가 뜨죠?
이 경고의 목적은 응용 프로그램의 메모리 누수를 방지하는 것입니다.컴포넌트가 DOM에서 마운트 해제된 후 컴포넌트 상태가 갱신되면 메모리 누수가 있을 수 있지만 폴스 포지티브가 많은 것을 나타냅니다.
메모리 누전 여부를 어떻게 알 수 있습니까?
컴포넌트보다 수명이 긴 객체가 직접 또는 간접적으로 해당 객체에 대한 참조를 유지하면 메모리 누수가 발생합니다.이 문제는 일반적으로 구성 요소가 DOM에서 마운트 해제될 때 구독을 취소하지 않고 이벤트나 변경 사항을 구독할 때 발생합니다.
통상은 다음과 같습니다.
useEffect(() => {
function handleChange() {
setState(store.getState())
}
// "store" lives longer than the component,
// and will hold a reference to the handleChange function.
// Preventing the component to be garbage collected after
// unmount.
store.subscribe(handleChange)
// Uncomment the line below to avoid memory leak in your component
// return () => store.unsubscribe(handleChange)
}, [])
서 ★★★★★store는 React 트리 상층(컨텍스트공급자 내) 또는 글로벌/모듈 범위 내에 존재하는 객체입니다. 예로는 를 들 수 있습니다.
useEffect(() => {
function handleScroll() {
setState(window.scrollY)
}
// document is an object in global scope, and will hold a reference
// to the handleScroll function, preventing garbage collection
document.addEventListener('scroll', handleScroll)
// Uncomment the line below to avoid memory leak in your component
// return () => document.removeEventListener(handleScroll)
}, [])
주의할 필요가 있는 또 하나의 예는 Web API입니다.Web API는 콜을 잊어버린 경우에도 메모리 누수가 발생할 수 있습니다.clearInterval마운트 해제 시.
하지만 그건 내가 하는 일이 아닌데 왜 이 경고에 신경을 써야 하죠?
구성 요소가 마운트 해제된 후 상태 업데이트가 발생할 때마다 경고하는 React의 전략은 많은 잘못된 긍정을 생성합니다.지금까지 본 것 중 가장 일반적인 것은 비동기 네트워크 요청 후에 상태를 설정하는 것입니다.
async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}
컴포넌트가 더 이상 필요하지 않게 된 후 즉시 해제되지 않기 때문에 기술적으로도 메모리 누수라고 주장할 수 있습니다.「투고」를 완료하는 데 시간이 걸리는 경우는, 메모리를 해방하는 데 시간이 걸립니다.하지만, 이것은 결국 쓰레기가 수거될 것이기 때문에 걱정할 필요가 없습니다.이 경우 경고를 무시할 수 있습니다.
그런데 경고문이 너무 거슬리는데, 어떻게 없애야 하죠?
stackoverflow에는 컴포넌트의 마운트 상태를 추적하고 상태 업데이트를 if 스테이트먼트로 정리할 것을 제안하는 많은 블로그와 답변이 있습니다.
let isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!isMountedRef.current) {
setPending(false)
}
}
이는 권장되지 않는 접근법입니다.코드를 읽기 어렵게 만들고 런타임 오버헤드를 추가할 뿐만 아니라 향후 React 기능에서도 제대로 작동하지 않을 수 있습니다.또, 「메모리 누전」에 대해서는 아무것도 행해지지 않습니다.이 컴포넌트는, 추가 코드가 없는 한, 계속 존속합니다.
이 문제를 해결하려면 비동기 함수(AbortController API 등)를 취소하거나 무시하는 것이 좋습니다.
실제로 React 개발 팀은 잘못된 긍정을 피하는 것이 너무 어렵다는 사실을 인식하고 React v18의 경고를 제거했습니다.
이와 같은 상태를 설정하여 컴포넌트가 마운트되었는지 여부를 확인할 수 있습니다.이렇게 하면 컴포넌트가 마운트 해제되어도 무언가를 취득하려고 하지 않습니다.
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if(!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
이게 도움이 되길 바라.
저도 같은 문제가 있었습니다만, @CalosVallejo의 답변으로 해결되었습니다:) 감사합니다!!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className="back-to-top">
<h1
className="scrollTop"
onClick={scrollTop}
style={{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};
이 오류는 다른 컴포넌트로 이동한 후 현재 컴포넌트의 상태 업데이트를 수행할 때 발생합니다.
예를들면
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
dispatch(login(res.data.data)); // line#5 logging user in
setSigningIn(false); // line#6 updating some state
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
#5행으로 login그러면 사용자가 대시보드로 이동하므로 로그인 화면이 마운트 해제됩니다.
리액트 가 #요?Respect Native ® #6 # now now now now now now now now now 。login component더 이상 없어요.
솔루션:
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
setSigningIn(false); // line#6 updating some state -- moved this line up
dispatch(login(res.data.data)); // line#5 logging user in
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
리액션 상태 업데이트 위쪽으로 이동하면 됩니다. 6번 라인을 5번 라인 위로 이동하면 됩니다.
이제 사용자를 이동하기 전에 상태가 업데이트되고 있습니다. ★★★
같은 경고가 표시됩니다.This solution for me - >
useEffect(() => {
const unsubscribe = fetchData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
둘 이상의 가져오기 함수가 있는 경우
const getData = () => {
fetch1();
fetch2();
fetch3();
}
useEffect(() => {
const unsubscribe = getData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
, 하면 더 , 더 쉽게 할 수 있을 것 .abort 이 : (이 문제를 해결했는가?)
useEffect(() => {
// get abortion variables
let abortController = new AbortController();
let aborted = abortController.signal.aborted; // true || false
async function fetchResults() {
let response = await fetch(`[WEBSITE LINK]`);
let data = await response.json();
aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
if (aborted === false) {
// All your 'set states' inside this kind of 'if' statement
setState(data);
}
}
fetchResults();
return () => {
abortController.abort();
};
}, [])
기타 방법 : https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8
사용자가 네비게이트를 하거나 다른 이유로 컴포넌트가 파괴된 후 비동기 콜이 돌아와 setState를 시도하면 오류가 발생합니다.실제로 종료된 비동기 호출일 경우 일반적으로 무해합니다.오류를 잠재울 수 있는 몇 가지 방법이 있습니다.
다음과 같은 훅을 구현할 경우useAsync와 함께 할 수 .letconst로 설정합니다.useEffect는 setState 함수를 no-op 함수로 설정합니다.
export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
let [parameters, setParameters] = useState(rest);
if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
setParameters(rest);
const refresh: () => void = useCallback(() => {
const promise: Promise<T | void> = gettor
.apply(null, parameters)
.then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
.catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
return promise;
}, [gettor, parameters]);
useEffect(() => {
refresh();
// and for when async finishes after user navs away //////////
return () => { setTuple = setParameters = (() => undefined) }
}, [refresh]);
let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
return tuple;
}
컴포넌트에서는 잘 동작하지 않습니다.여기서 useState를 마운트/언마운트된 함수로 랩하고 반환된 setState 함수를 if-check로 랩할 수 있습니다.
export const MyComponent = () => {
const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
// ..etc.
// imported from elsewhere ////
export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
const [val, setVal] = useStateTuple;
const [isMounted, setIsMounted] = useState(true);
useEffect(() => () => setIsMounted(false), []);
return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}
'보다 낫다'를 수 .useStateAsync이치노
export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
return useUnlessUnmounted(useState(initialState));
}
useEffect에 종속성을 추가합니다.
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [fetchData, props.spotifyAPI])
일반적으로 이 문제는 다음과 같이 구성 요소를 조건부로 표시할 때 발생합니다.
showModal && <Modal onClose={toggleModal}/>
도 이 놀이기구에서 를 부지기수입니다.ModalonClose 함수, 예를 들어
setTimeout(onClose, 0)
이것으로 충분합니다. : )
const [state, setState] = useState({});
useEffect( async ()=>{
let data= await props.data; // data from API too
setState(users);
},[props.data]);
React Native iOS에서 이 문제가 발생하여 setState 호출을 캐치로 이동하여 수정했습니다.아래를 참조해 주세요.
잘못된 코드(오류의 원인):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
}
setLoading(false) // this line was OUTSIDE the catch call and triggered an error!
}
정상적인 코드(오류 없음):
const signupHandler = async (email, password) => {
setLoading(true)
try {
const token = await createUser(email, password)
authContext.authenticate(token)
} catch (error) {
Alert.alert('Error', 'Could not create user.')
setLoading(false) // moving this line INTO the catch call resolved the error!
}
}
내 앱에서도 비슷한 문제가 있는데useEffect일부 데이터를 가져온 다음 상태를 업데이트합니다.
useEffect(() => {
const fetchUser = async() => {
const {
data: {
queryUser
},
} = await authFetch.get(`/auth/getUser?userId=${createdBy}`);
setBlogUser(queryUser);
};
fetchUser();
return () => {
setBlogUser(null);
};
}, [_id]);
이것은 카를로스 발레호의 대답보다 낫다.
useEffect(() => {
let abortController = new AbortController();
// your async action is here
return () => {
abortController.abort();
}
}, []);
위의 코드에서 Abort Controller를 사용하여 효과를 해제했습니다.동기 액션이 완료되면 컨트롤러를 중단하고 효과를 등록 해제합니다.
그것은 나에게 효과가 있다.
쉬운 방법
let fetchingFunction= async()=>{
// fetching
}
React.useEffect(() => {
fetchingFunction();
return () => {
fetchingFunction= null
}
}, [])
options=필터입력: "checkbox" , textLabels: { body: { noMatch: isLoading?}: '죄송합니다. 표시할 일치하는 데이터가 없습니다', ,}}}
언급URL : https://stackoverflow.com/questions/54954385/react-useeffect-causing-cant-perform-a-react-state-update-on-an-unmounted-comp
'programing' 카테고리의 다른 글
| 변수가 javascript에서 typeed 배열인지 확인하는 방법 (0) | 2023.03.08 |
|---|---|
| JavaScript를 사용하여 파일 다운로드 시작 (0) | 2023.03.08 |
| 키로 특정 JSON 값을 찾는 방법 (0) | 2023.03.08 |
| spring-boot 어플리케이션에 @EnableWebMvc가 필요 없는 이유 (0) | 2023.03.08 |
| ReactJs의 prevState란 무엇입니까? (0) | 2023.03.08 |