[React]useState, useEffect 알고 쓰자

hyunwoo Jin·2023년 5월 12일
0
post-thumbnail
post-custom-banner

useState useEffect 알고 쓰자

저는 평소에 useState useEffect 를 자주 사용합니다. 동작 원리를 모르고 사용하다 보니 나중에 오류가 나도 원인을 모르게 됩니다.그리고 올바르게 사용하고 있는 지도 모릅니다. react hooks의 종류에는 여러가지들이 있지만 이번 글에서는 제가 많이 사용했던 useState useEffect 두가지에 대해서 파먹어보도록 하겠습니다.

React hooks?

class 컴포넌트에는 라이프사이클 메서드가 존재합니다. 생명주기를 이용해서 컴포넌트가 생성되고 제거되는 과정의 특정시점에서 코드를 실행시킬 수 있는 것이죠. 하지만 함수형 컴포넌트 기반으로 React에서 해당 메서드가 필요할 경우 class를 이용한 코드를 작성해야 합니다. 이를 위해서 등장한 라이브러리가 React hooks 입니다. 19년 2월에 데이터 캐시가 개발되어 함수 컴포넌트가 어떤 값을 유지하고 이를 재사용할 수 있도록 캐시 가 만들어졌습니다. 그리고 이 캐시를 이용하고자 만든 여러개의 API가 React Hooks 입니다.

함수형 컴포넌트에서도 라이프사이클과 state 관리를 하게 해주는 라이브러리!

React hooks 를 사용하는 이유

  • class코드 없이 함수형 컴포넌트 기반에서 생명주기 메서드와 state 관리를 위해서 사용합니다.
  • class형 컴포넌트에서는 higher-order components(?) 에서 페이지 내에 재사용가능한 로직을 분리했는 데 이를 위해 많은 양의 wrapper component 가 생성되고 wrapper hell을 초래하는 데 hook에서는 이를 해결할 수 있습니다.
  • class형 컴포넌트보다 빠른 성능과 짧은 코드로 실행시킬 수 있습니다. 예를 들면 class형의 경우 componentDidMount componentDidUpdate componentWillUpdate 세가지 모두 다르게 처리하지만 react hooks는 useEffect 에서 모두 처리가 가능합니다.

React hooks 사용규칙

  1. hook 은 여러번 호출이 가능합니다. 하지만 함수형 컴포넌트 내에서만 작동합니다. 복합 실행문{} 반복문에서는 사용할 수 없습니다.

    const App = () => {
      return {
        <div>
          <div>{const [value, setvalue] = useState()}</div>
        </div>
      }
    }
  2. 비동기 함수는 콜백으로 사용할 수 없습니다.

    const App = () => {
      useEffect(async () => {
        await Promise.resolve(1) // 땡!!
      }, [])
    
      return {
        <div>
          <div>Test</div>
        </div>
      }
    }

hook의 종류

  • useState
  • useEffect
  • useContext
  • useReducer
  • useRef
  • forwardRef (뭐꼬이건)
  • useImperativehandle (이건또 모꼬)
  • useMemo / useCallback
  • useLayoutEffect
  • useDebugValue

제가 생각한 것보다 종류가 엄청 다양했습니다..--;
쨌든 이제 useState 와 useEffect에 대해 파고 들어봅시다.

useState

state 는 react에서 사용자의 액션에 따라 화면에 다르게 출력(렌더)시키기 위해 사용되는 트리거역할의 변수입니다.
react는 setState 함수를 통해서 state의 값을 변경시킵니다.
setState는 state의 값을 즉시 변경하지 않고 현재 state와 setState의 인자를 비교하여 변경에 따른 정보를 리렌더링시키기는데 이때 state에 변경된 값이 부여됩니다.

  • example 1

      import {useState} from 'react'
    
      const App = () => {
        const [count, setCount] = useState(0);
        setCount(count + 1);
        console.log(count); // console : 0;
      }

    예제 코드를 보겠습니다. count 라는 이름의 state 가 생성되었습니다. 이 후 바로 setCount가 실행되면서 count 의 값에 +1 를 해주었습니다.

    • 하지만 다음 코드에서 출력해보면 count 의 값은 여전히 0 입니다.
    • 이유는 setCount(setState)는 비동기 함수이며 렌더링을 마친 후, 즉 count 값이 사용되는 모든 곳의 코드가 실행을 마친 후에 실행 되기 때문입니다.
    • 렌더링을 마친 당시의 count 값과 setCount 내의 값을 비교해 값의 변화가 있을 시 리렌더링됩니다.(이때 react는 virtual DOM을 사용하여 기존의 dom tree 와 변경된 count를 적용한 dom tree 내 차이가 발생한 노드에 한해서 부분 렌더링함)
  • example 2

    import {useState} from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
      setCount(count++);
      setCount(count++);
      setCount(count++);
    }

    setCount가 세번 실행되었습니다. +1을 세번했으니 count의 값은 3이겠죠?
    하지만 업데이트 시 count 의 값은 3이 아닌 1입니다.

    • setCount의 결과값은 렌더링을 마친 후에 업데이트 과정에서 count에 적용되기 때문입니다.
    • 그렇기 때문에 위 setCount 내 count의 값은 모두 0입니다.
    • 이후 컴포넌트가 업데이트되고 나면 setCount의 결과값은 모두 1로 동일하고 count의 값은 1로 변경됩니다.

    변경될 count의 값을 업데이트 이전에 접근할 수는 없을까요? 있습니다!

  • example 3

    import {useState} from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
      setCount((current) => current++);
      setCount((current) => current++);
      setCount((current) => current++);
    }

    이전 예제와 비슷해 보이지만 분명히 다른 부분이 있네요. setCount의 인자가 함수라는 점입니다. setState는 개체와 함수 두가지의 인자를 허용하는데요. 예제2는 개체이고 예시3은 함수를 인자로 전달하고 있습니다.

    • 함수를 인자로 사용할 경우 파라미터로 임시 저장된 state 값을 사용할 수 있습니다.여기서 임시저장된 state 값이란 리렌더링 시에 변경에 따라 업데이트될 count에 접근할 수 있다는 것으로 보입니다.
    • 첫번째 setCount의 경우 이전의 setCount가 존재하지 않기 때문에 파라미터는 0일 것입니다.
    • 하지만 두번째 setCount 의 경우 이전에 current++ 를 통해 임시저장된 state 값은 1이 되었고 1을 이용해 새로운 값을 return 할 수 있습니다.

useEffect

렌더링 이후에 코드를 실행시킵니다. deps 배열을 이용해 특정 상황에서만 의존적으로 코드를 실행시킬 수 있습니다. useEffect의 첫번째 인자는 함수를 실행시키고 두번째 인자는 의존값이 들어간 배열(deps)입니다. deps의 변화를 감지하고 업데이트될때 첫번째 인자의 함수를 실행시킵니다.
형태는 대충 이렇습니다.

useEffect(()=> {
  deps의 값이 변경되었을때 해당코드 실행
},[deps])
  • example 1

    import { useEffect } from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const onClick = () => {
        setCount((current) => current++);  
      }
    
      useEffect(()=> {
        console.log('딩동')
      },[])
    }

    deps 내 어떠한 값도 없는 빈배열 상태입니다. 리액트 데이터에 관여하는 어떠한 값도 사용하지 않겠다는 뜻입니다. 컴포넌트 내 의존성 검사를 생략하고 단 한번만 실행합니다.

    • 컴포넌트의 마운트완료와 함께 '딩동'이 출력됩니다. (해당 시점은 class 형 컴포넌트에서 componentDidMount와 같습니다)
    • 가령 onClick이 발생하여 컴포넌트가 업데이트 되어도 '딩동'은 두번 다시 실행되지 않습니다.

    ⭐️ 빈 배열([])은 실제로 값이 사용되어야 할때 버그를 발생시키는 주된 원인이라고 합니다. 빈 배열을 이용해서 의존성 검사를 생략하는 것보다 의존성 검사를 필요로 하는 상황을 제거하는 방법들을 이용하는 것이 바람직하다고 합니다.(useReducer,useCallback 등)

  • example 2

    import { useState, useEffect } from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
      const onClick = () => {
        setCount((current) => current++);  
      }
    
      useEffect(()=> {
        console.log('딩동');
      })
    }

    두번째 인자가 없는 경우 컴포넌트가 마운트가 완료되거나 업데이트가 완료될 때마다 매번 '딩동' 이 출력됩니다.

    componentDidMount() || componentDidUpdate()
  • example 3

    import { useState, useEffect } from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
      const onClick = () => {
        setCount((current) => current++);  
      }
    
      useEffect(()=> {
        console.log(count);
        return ()=> {console.log('clean up : 'count)} // <- clean up
      })
    }

    이제 조금 이해하기 힘들 수 있습니다. useEffect 내에서 함수를 반환하고 있습니다.

    • useEffect에서 return하는 함수를 clean up 함수 라고 합니다.
    • 이 clean up 함수는 컴포넌트가 언마운트 직전에 실행됩니다.
    • App 컴포넌트가 생성되면 0(count)이 출력됩니다. componentDidMount() 하지만 컴포넌트가 제거되지 않았기 때문에 clean up 은 실행되지 않았습니다.
    • onClick 을 통해 count 의 값이 변화가 생기면 cleanup 의 count 값 0이 출력되고 컴포넌트가 업데이트됩니다. 이때 업데이트된 count 값 1이 출력됩니다. componenetDidUpdate()
      컴포넌트가 할 일을 마치고 제거되기 직전에 cleanup 에서 1이 출력됩니다. componentWillUnMount()
  • example 4

    import { useState, useEffect } from 'react'
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const onClick = () => {
        setCount((current) => current++);  
      }
    
      useEffect(()=> {
        console.log('딩동')
      },[count])
    }

    위 코드는 일반적인 useEffect 활용 예제라 할 수 있습니다. onClick 함수를 통해 count 의 값이 변경되면 console.log 가 실행되는 내용의 코드입니다.

    • 컴포넌트의 마운트(생성)과정이 끝남과 동시에 '딩동'이 출력됩니다.(==componentDidMount())
    • 이후 누군가의 이벤트로 인해 onClick의 setCount()가 실행되며 count의 변경으로 인한 리렌더링을 포함한 컴포넌트의 업데이트과정이 진행될 것입니다.
    • 렌더링되는 과정에서 useEffect는 마운트 당시의 count(deps)와 업데이트된 count(deps)를 비교합니다.
    • count가 변경되었다면 렌더링을 포함한 업데이트를 마친후 '딩동'이 출력됩니다. (== componentDidUpdate())

마치며

useState를 통해 상태를 관리해왔지만 state가 어떻게 변경되고 적용되는 지 새로 알게되었습니다. 또한 변경이 적용되기 이전에 임시저장된 값에 접근할 수 있다는 내용도 새롭게 알게 되었습니다.그리고 useEffect를 통해 다양한 시점에서 코드를 실행하는 방법도 배우게 되었습니다. 제 눈높이에 맞추어 풀고 풀어서 설명하는 바람에 내용에 비해 글이 길어진 점이 있습니다. 글로 써내려가며 다시 한번 이해한 내용을 점검하고 react hook에 대해 잘 모르시는 분들께라도 도움이 되었으면 합니다.혹시 틀린 내용이 있다면 댓글로 지적해주시면 감사하겠습니다.

참고자료

리액트공식문서
벨로퍼트
블로그

profile
꾸준함과 전문성
post-custom-banner

0개의 댓글