아.. 너무 고됐다.
잘못된 구글링이 얼마나 위험할 수 있는지 느꼈던 경험이었다.
시작 단추를 잘 못 끼워 맞춰서 한참을 해맸다.
stackoverflow에 아래의 예시와 같이 써진 코드를 참고해서 작성하는데, 한참을 해도 작동이 안됐었다.
useEffect(async () => {
const data = await fetchData();
}, [fetchData])
작동이 안됐던 이유는, useEffect의 첫번째 인자는 undefined 혹은 함수를 리턴하는데,
async함수는 Promise를 리턴하기 때문이라고 한다.
이것을 해결하기 위한 방법은, 비동기 함수 선언을 useEffect안에 넣어버리는 것이다.
useEffect(() => {
// declare the data fetching function
const fetchData = async () => {
const data = await fetch('https://yourapi.com');
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);
}, [])
여기서 한가지 더 주의해야 할 점은,
fetch해온 결과 값을 이용하고 싶을 때도 마찬가지로 비동기함수(fetchData함수) 안에 넣어야 한다는 것이다.
아래와 같이 밖에서 사용하는 것을 주의해야 한다 !
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch('https://yourapi.com');
// convert data to json
const json = await data.json();
return json;
}
// call the function
const result = fetchData()
// make sure to catch any error
.catch(console.error);;
// ❌ don't do this, it won't work as you expect!
setData(result);
}, [])
setData(result)가 실행될 때 result의 값이 어떤 값일지 확신할 수 없기 때문이다.
result의 값은 프로미스의 펜딩상태를 유지하게 된다. 콘솔로 result를 확인해 본다면 아래와 같은 결과를 보게 된다.
Promise {<pending>}
setData(result)를 비동기함수 '안'에 넣음으로서 문제를 해결할 수 있었다.
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch('https://yourapi.com');
// convert the data to json
const json = await response.json();
// set state with the result
setData(json);
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [])
만약 어떤 이유에 의해 결과 값을 이용하는 코드를 밖에서 사용하고 싶다면 방법이 없는 것은 아니다.
useCallback 을 사용하는 방법이다.
왜 useCallback을 사용해야 하냐면,
useEffect밖에서 함수를 선언하게 되면 dependency안에 해당 함수를 넣어야 한다. 그런데 이것이 무한렌더링을 초래하기 때문이다.
useCallback 은 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값 (dependency) 이 변경될 때까지 재렌더링 시키지 않는다.
const fetchData = useCallback(async () => {
const data = await fetch('https://yourapi.com');
setData(data);
}, [])
// the useEffect is only there to call `fetchData` at the right time
useEffect(() => {
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [fetchData])
내부에서 setData해준 코드로 돌아가서,
data를 fetch해오는 주소값에 들어갈 param이 있다면, param을 디펜던시 안에 넣음으로서 동적으로 useEffect를 실행시킬 수 있다.
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch(`https://yourapi.com?param=${param}`);
// convert the data to json
const json = await response.json();
// set state with the result
setData(json);
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [param])
이 때 param의 값이 바뀌면서 fetchData 함수가 두번 호출되게 되는데, 이 과정이 빠르게 일어난다면 race condition 문제가 생기게 된다. race condition 문제란 첫번째 호출 이후 두번째 호출이 일어났음에도 첫번째 호출 값으로 상태가 유지되는 것이다.
이 때는 아래와 같이 상태값을 없데이트 할 지 말지 결정하는 변수를 만들어서 해결할 수 있다.
useEffect(() => {
let isSubscribed = true;
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch(`https://yourapi.com?param=${param}`);
// convert the data to json
const json = await response.json();
// set state with the result if `isSubscribed` is true
if (isSubscribed) {
setData(json);
}
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
// cancel any future `setData`
return () => isSubscribed = false;
}, [param])
♪
참고사이트
https://devtrium.com/posts/async-functions-useeffect
'Frontend Study - 2 > React' 카테고리의 다른 글
React : 컴포넌트의 재사용 with children - Wrap Component (확장하기) (0) | 2022.11.23 |
---|---|
React : Immer 사용하기 (0) | 2022.11.22 |
React : useCallback 성능 최적화 어디에 사용하나? + 함수의 재사용, 객체 데이터의 저장과정. (0) | 2022.08.28 |
React : Context API - props 없이 state 공유 하기. (0) | 2022.08.26 |
React : 성능개선 lazy , memo, useMemo , useTransition, useDeferredValue (0) | 2022.08.20 |