useEffect 잘 알고 쓰고 있을까?

isTuna·2022년 3월 13일
43

Typescript

목록 보기
2/2
post-thumbnail

📝 이 포스트는 useEffect in react: Everything you need to know를 참고한 글입니다!

useEffect란?

React로 개발할 때 DOM 업데이트 직후에 함수를 실행해야 할 필요가 많았습니다. 그럴때 우리는 Hooks 이전에는 ComponentDidMount, ComponentDidUpdate로 해결했었고, 현재는 useEffect를 사용하는 경우가 많습니다.

useEffect는 함수형 컴포넌트에서 side effects들을 실행할 때 사용됩니다. 우리는 useEffect라는 Hook을 이용해서 DOM 렌더링 직후에 컴포넌트가 할 일을 지시합니다. 많은 경우 네트워크 요청(데이터 요청) 또는 수동으로 DOM 변화를 지시합니다.

예시로 보는 useEffect

아래는 useEffect를 사용해 구현한 간단한 카운터입니다.

import {useEffect, useState} from "react";

export default function useEffectTest() {
    const [count, setCount] = useState(0);

    useEffect(()=> {
       console.log("useEffect 실행! ",count);
    });

    console.log("렌더 이전 실행!");

    const clickHandler = () => {
        setCount(count + 1);
        console.log("카운터 + 1 ",count);
    }

    return(
        <div style={{textAlign:"center",marginTop:"10px"}}>
            <h1>{count}</h1>
            <button onClick={clickHandler}> + </button>
        </div>
    )
}

초기 렌더가 끝나면 console에는 아래와 같은 문구가 출력되는 것을 확인 할 수 있습니다. 아직 + 버튼을 누르기 전입니다.

useEffect가 함수를 실행하기 전에 컴포넌트 안에 있는 렌더 이전 실행!을 출력하는 함수가 먼저 실행됩니다. clickHandler 함수는 + 버튼을 누르기 전이므로 한번도 실행되지 않았습니다. 이제 한번 버튼을 눌러봅시다!

버튼을 한번 누르게 되면 위와 같은 순서로 console창에 출력됩니다. 위와 같은 순서로 실행된 이유를 하나씩 봅시다.

  1. onClick의 결과로 clickHandler가 실행되어 카운터 + 1 0이 출력됩니다.

  2. clickHandler안에는 state를 변환하는 함수가 있지만 출력은 이전 state0을 보여줍니다. 이것은 함수 내부에서 state가 업데이트 되는 경우에 그 함수의 변환된 현재 값을 사용하기 위해서는 해당 함수 밖에서 사용해야 하기 때문입니다.

  3. 컴포넌트 전체가 실행되기 때문에 컴포넌트 내부에서 렌더 이전 실행!을 출력합니다.

  4. 이 시점에서 View가 수정되어 화면의 숫자가 0에서 1로 변환됩니다.

  5. DOM이 업데이트 되었기 때문에 useEffect가 실행됩니다.

이제 우리는 useEffect가 view의 렌더가 끝났을 때야 실행된다는 것을 이해했습니다. 그럼 왜 렌더가 끝났을때 실행되는 것 일까요?

렌더가 끝 마쳤을때야 비로소 state가 변환되는 이유는?

그 이유로는 사용자가 View에만 신경 쓰기 때문이라고 합니다. 사용자가 console 로그나, localStorage와 같은 side effect들을 생각하지 않기 때문에 화면에서는 즉각적으로 state가 변하고 뒷단에서는 늦게 변환되어집니다.
state 변환과 view 렌더 사이에 작업이 있으면 렌더가 느려지고 사용자가 부정적인 경험을 하게 됩니다.

Dependancy Array

useEffect를 사용할때 두번째 인자로 Dependancy Array를 넣어주게 됩니다. Dependancy Array는 이름처럼 useEffect가 의존성을 가질 상태 값들을 넣는 배열입니다. 아래의 표와 같은 값들이 들어갈 수 있습니다.

위의 표의 상황들을 하나씩 예로 보여드리겠습니다.

Case 1 | no value passed

import {useEffect, useState} from "react";

export default function useEffectTest() {
    const [input, setInput] = useState("");
    const [label, setLabel] = useState("");

    useEffect(()=> {
       console.log("useEffect 실행! ");
    });

    return(
        <div style={{textAlign:"center",marginTop:"10px"}}>
            <h1>Input</h1>
            <input onChange={ e => setInput(e.target.value) } style={{marginBottom:"10px"}}></input>

            <h1>Buttons</h1>
            <button onClick={() => setLabel("강아지")}>강아지</button>
            <button onClick={() => setLabel("고양이")}>고양이</button>
            <button onClick={() => setLabel("다람쥐")}>다람쥐</button>
        </div>
    )
}

Dependancy Array를 사용하지 않았을 경우입니다. 이 경우에 모든 렌더와 모든 상태 값 변환마다 한번씩 useEffect안의 함수가 실행됩니다.

Case 2 | empty array passed

useEffect의 Dependancy ArrayOptional이기 때문에 사용할수도 사용하지 않을 수도, 빈 배열을 줄 수도 있습니다.

useEffect(()=> {
       console.log("useEffect 실행! ");
}, []);

많은 경우 위와 같이 빈 배열을 주는데 이 경우는 최초 렌더 1회에만 useEffect 내부의 함수가 실행됩니다.

Case 3 | array value (one state variable)

이번에는 Dependancy Array에 값을 준 경우입니다. 위의 코드에서는 input, label state가 2개 있습니다. 이 중에 특정 상태 값이 변경 될 때만 함수가 실행되는 useEffect를 만들 수 있습니다.

useEffect(()=> {
       console.log("useEffect 실행! ");
}, [label]);

input 상태 값이 변경될 때는 useEffect 내부 함수가 실행되지 않는 것을 확인할 수 있습니다.

Case 4 | array value (multiple state variables)

마지막으로 배열에 여러개의 state들을 넣는 경우입니다. 이 경우에는 배열에 들어있는 모든 state들에 의존성을 부여합니다.

useEffect(()=> {
       console.log("useEffect 실행! ");
}, [label, input]);

이 경우는 맨 처음에 아무 값도 주지 않았을 때와 같은 모습을 보여줍니다. 하지만 이것은 해당 컴포넌트에 2개의 state만 사용하기 때문이지 같다고 할 수는 없습니다.

async / await

useEffect를 사용할 때 많은 경우 API 호출을 합니다. 페이지에 진입 했을때 데이터를 불러오기 위해서 네트워크 통신을 렌더가 끝난 직후 실행해 데이터를 불러옵니다. 서버와의 통신은 비동기로 처리하기 때문에 useEffect 내부 함수를 async / await로 처리하는데 이것은 잘못된 방식입니다.

😈 😈 😈 😈
useEffect( async () => {
  await load();
},[])

useEffect와 같은 Hook들은 함수를 반환해야하는데 async를 사용할 시에 Promise 객체를 반환하게 됩니다.

그렇다고 useEffect로 네트워크 통신을 못하는 것은 아닙니다. 비동기 처리 함수를 따로 정의해주면 됩니다.

비동기 처리 방법

😇
useEffect( () => {
  (async () => {
    await load();
  })();
},[])

위와 같이 비동기 처리를 하는 함수를 useEffect 함수 안에 정의하면 쉽게 원하는 동작을 할 수 있습니다.


profile
청소연구소 개발자 (2021. 05~ )

12개의 댓글

comment-user-thumbnail
2022년 3월 13일

좋은 글 잘 봤습니다~

1개의 답글
comment-user-thumbnail
2022년 3월 14일

다음번엔 useLayoutEffect 도 설명해주세요~!

1개의 답글
comment-user-thumbnail
2022년 3월 17일

만약 종속성에 객체를 준다면 어떻게될까요?

3개의 답글
comment-user-thumbnail
2022년 3월 17일

잘 봤습니다:)

1개의 답글
comment-user-thumbnail
2022년 3월 20일

정리된 글 잘 봤습니다 ㅎㅎ
useEffect에 첫 번째 인자로 전달하는 콜백함수가 리턴하는 콜백함수의 역할을 같이 정리해도 좋을 것 같아요!

1개의 답글