주니어 개발자들이 useEffect를 사용할때 자주 하는 실수 2편.

엄강우·2022년 11월 23일
0

You don't know React

목록 보기
9/9

저번 포스팅에 이어서 계속해서 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로 이동하는 링크가 있고 그 링크를 클릭하고 데이터를 패칭하면 alertsetPosts를 하게 됩니다. 하지만 데이터를 받아오는데 시간이 오래 걸려서 미처 데이터를 받기 전에 다른 페이지로 넘어가게 된다면? 갑자기 다른 페이지에서 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라는 변수를 만들어서 조건을 달아 주었습니다. 그리고 cleanUpisCancelled변수를 true로 만들어주어서 데이터를 미쳐 다 받지 못한 상태에서 페이지를 바꾼다면 isCancelledtrue가 되기 때문에 alertsetPosts는 실행되지 않을 것입니다.

또 다른 예를 한번 보겠습니다.

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를 통해 핸들링 할 수 있죠. 그럼 우리는 유저친화적인 개발자에 한발 더 내딛었다고 볼 수 있지 않을까요?

보너스 Strict Mode

react앱은 기본적으로 develop버젼에서는 strictMode아래에서 실행된다. 이는 리액트의 기능을 사용함으로 생길 수 있는 잠재적 버그들을 해결하기 위함으로 production 모드에서는 실행되지 않으니 배포할때 걱정할 필요는 없다. 하지만 StrictMode를 켜고 useEffect를 이용하면 아무래도 제대로 알아보기 어렵습니다. 그래서 useEffect가 어떻게 동작하는지 제대로 알기 위해서는 잠시 StrictMode를 꺼두고 실험해보시기 바랍니다.
하지만 기능을 놓고 보았을때 StrictMode를 켠 상태에서는 제대로 동작하지 않지만 StrictMode를 끈 상태에서만 제대로 동작한다면 이는 문제가 있는 구현이니 반드시 고쳐야 합니다.

출처

profile
안녕하세요 프론트엔드 개발자를 꿈꾸는 엄강우입니다.

0개의 댓글