저번 포스팅에 이어서 계속해서 useEffect
에 대해 알아 보겠습니다.
이번 포스팅의 주제는 cleanUp
입니다.
function App() {
const [number, setNumber] = useState(0)
useEffect(() => {
console.log("effect")
setInterval(() => {
setNumber(prev => prev+1)
}, 1000)
}, [])
return <div>결과 : {number}</div>
}
다음과 같이 useEffect
를 사용해서 1초에 1씩 늘어나게 만들었습니다. 그럼 무엇이 문제일까요? 정답은 이 컴포넌트를 마운트 할때마다 setInterval
이 쌓인다는 것입니다. 그럼 우리가 원하는 동작을 하지 않게 됩니다. 즉 1번 마운트하면 1초에 1씩 늘어나지만 2번 마운트하면 1초에 2씩 늘어나는 것이죠.
이런 문제를 해결할 수 있는 것이 cleanUp
입니다.
const [number, setNumber] = useState(0)
useEffect(() => {
console.log("effect")
const interval = setInterval(() => {
setNumber(prev => prev+1)
}, 1000)
// 여기서 리턴하는 익명함수가 cleanUp함수입니다.
return () => {
clearInterval(interval)
}
}, [])
return <div>결과 : {number}</div>
cleanUp
함수는 컴포넌트가 언마운트 될때 실행 됩니다. 그럼 이 컴포넌트가 실행되고 다시 마운트 되기전에 반드시 언마운트 되기때문에 언마운트로 인해 기존 interval
이 없어지고 다시 마운트 되면서 오직 하나의 interval
만 컴포넌트에 존재하게 됩니다. 그럼 우리가 원하게끔 페이지가 동작할 것입니다.
그럼 또 어떤 곳에서 사용할 수 있을까요?
Link
를 통한 페이지 이동에도 cleanUp
함수를 활용하면 더욱 유저친화적인 페이지를 구현 할 수 있습니다.
function Home() {
return (
<div>
<Link to="/posts">Go to posts</Link>
</div>
)
}
function Post() {
const [posts, setPosts] = useState({})
useEffect(() => {
fetch("어떤 주소")
.then((res) => res.json())
.then((data) => {
alert("posts are ready, updating state")
setPosts(data)
})
},[])
return <div>무언가</div>
}
다음과 같이 Home에서 Post로 이동하는 링크가 있고 그 링크를 클릭하고 데이터를 패칭하면 alert
와 setPosts
를 하게 됩니다. 하지만 데이터를 받아오는데 시간이 오래 걸려서 미처 데이터를 받기 전에 다른 페이지로 넘어가게 된다면? 갑자기 다른 페이지에서 alert
가 뜨는 우리가 원하지 않는 현상이 벌어질 것 입니다. 우리는 이것을 cleanUp
함수를 이용해 해결할 수 있습니다.
function Post() {
const [posts, setPosts] = useState({})
useEffect(() => {
let isCancelled = false
fetch("어떤 주소")
.then((res) => res.json())
.then((data) => {
if (!isCancelled) {
alert("posts are ready, updating state")
setPosts(data)
}
})
return () => {
isCancelled = true
}
},[])
return <div>무언가</div>
}
다음과 같이 isCancelled
라는 변수를 만들어서 조건을 달아 주었습니다. 그리고 cleanUp
는 isCancelled
변수를 true
로 만들어주어서 데이터를 미쳐 다 받지 못한 상태에서 페이지를 바꾼다면 isCancelled
가 true
가 되기 때문에 alert
와 setPosts
는 실행되지 않을 것입니다.
또 다른 예를 한번 보겠습니다.
function User() {
const [user, setUser] = useState({})
const id = useLocation().pathname.split("/")[2]
useEffect(() => {
fetch(`어떤 주소/${id}`)
.then((res) => res.json())
.then((data) => {
setUser(data)
})
}, [id])
return (
<div>
<p>name : {user.name}</p>
<p>Username : {user.userName}</p>
<Link to="/users/1">Fetch User 1</Link>
<Link to="/users/2">Fetch User 2</Link>
<Link to="/users/3">Fetch User 3</Link>
</div>
)
}
다음과 같이 id
에 따라 페이지에 표시되는 유저를 바꿀 수 잇는 페이지 입니다. 우리가 링크를 하나씩 클릭 했을때는 아마 잘 동작할 것입니다. 그런데 만약 링크를 순서대로 하나씩 다 클릭한다면? 어떤 일이 일어날까요? 아마 페이지의 정보가 순서대로 1, 2, 3번 유저의 정보로 바뀔 것입니다. 이것은 아마 우리가 의도한 동작이 아닐것입니다. 그럼 이 문제를 어떻게 해결해야 할까요?
function User() {
const [user, setUser] = useState({})
const id = useLocation().pathname.split("/")[2]
useEffect(() => {
const controller = AbortController()
const { signal } = controller
fetch(`어떤 주소/${id}`, { signal })
.then((res) => res.json())
.then((data) => {
setUser(data)
}).catch(err => {
if (err === "AbortError") {
console.log("fetch cancelled")
} else {
// handle something error
}
})
return () => {
controller.abort()
}
}, [id])
return (
<div>
<p>name : {user.name}</p>
<p>Username : {user.userName}</p>
<Link to="/users/1">Fetch User 1</Link>
<Link to="/users/2">Fetch User 2</Link>
<Link to="/users/3">Fetch User 3</Link>
</div>
)
}
AbortController
를 사용하면 데이터가 패치하다가 abort
할 수 있습니다. 그리고 결과 에러를 일으키는데 우리는 그것을 catch
를 통해 핸들링 할 수 있죠. 그럼 우리는 유저친화적인 개발자에 한발 더 내딛었다고 볼 수 있지 않을까요?
react앱은 기본적으로 develop버젼에서는 strictMode아래에서 실행된다. 이는 리액트의 기능을 사용함으로 생길 수 있는 잠재적 버그들을 해결하기 위함으로 production 모드에서는 실행되지 않으니 배포할때 걱정할 필요는 없다. 하지만 StrictMode를 켜고 useEffect
를 이용하면 아무래도 제대로 알아보기 어렵습니다. 그래서 useEffect
가 어떻게 동작하는지 제대로 알기 위해서는 잠시 StrictMode를 꺼두고 실험해보시기 바랍니다.
하지만 기능을 놓고 보았을때 StrictMode를 켠 상태에서는 제대로 동작하지 않지만 StrictMode를 끈 상태에서만 제대로 동작한다면 이는 문제가 있는 구현이니 반드시 고쳐야 합니다.