리액트 useEffect

hyun·2022년 4월 22일
2

React Hooks

목록 보기
3/5
post-thumbnail

이 포스트에서 다룰 것

리액트 앱을 개발하면서 특정 컴포넌트의 렌더링 직전에 특정 작업을 해야 한다던지, 업데이트 된 후에 처리해야 한다던지 하는 일이 있을 것이다.

이런 상황에서 유용하게 쓰일 수 있는 useEffect 훅의 개념과 사용법과 의존성 배열에 대해서 알아본다.

컴포넌트의 생명 주기 (Lifecycle)

어떤 컴포넌트가 화면에 첫 렌더링이 되는 것을 마운트(mount), 다시 렌더링 되는 것을 업데이트(Update), 화면에서 사라지는 것을 언마운트(Unmount)라고 한다. 각각의 상황에서 특정 작업을 실행시키고 싶다면 useEffect를 사용할 수 있다.

useEffect 사용법

useEffect는 두 가지 형태만 알고 있으면 된다.

useEffect(() => { /* 원하는 작업 */ });
useEffect(() => { /* 원하는 작업 */ }, [value]);

첫 번째 형태는 useEffect의 인자로 하나의 콜백함수가 들어가는 형태, 두 번째는 콜백함수와 배열을 받는 형태이다. 이 배열은 다른 이름으로 dependency array, 의존성 배열이라고도 한다.

첫 번째 형태는 컴포넌트가 렌더링 될 때마다 매번 콜백함수가 실행된다. 즉 컴포넌트가 맨 처음에 마운트 될 때, 업데이트 될 때마다 실행된다.

두 번째 형태는 매번 렌더링이 될 때마다 실행하는 것이 아니라, 컴포넌트가 맨 처음 화면에 렌더링이 될 때, 그리고 의존성 배열 요소값이 바뀔 때만 실행이 된다. 의존성 배열은 콜백함수가 실행되는 일종의 조건을 거는 기능을 한다고 볼 수도 있다.

useEffect(() => {}, []);

위와 같이 의존성 배열에 빈 배열을 전달한다면 배열의 요소가 변할 일이 없으므로 맨 처음 컴포넌트가 렌더링될때만 콜백함수가 실행된다.

Clean Up

우리가 useEffect를 사용해서 누군가를 팔로우하는 기능을 넣었다면, 이후에 언팔로우를 할 수도 있다. 또는 어떤 이벤트리스너를 등록했다가 더이상 필요가 없어지면 이 이벤트리스너를 정리해주는 작업을 해야 한다. 이런 정리 작업을 하려면 useEffect의 return 값으로 함수를 넣어주면 된다.

useEffect(()=>{
	// 팔로우
  return () => {
  	// 언팔로우
  }
}, [])

이 콜백 함수 안에서 우리가 원하는 정리작업을 처리해주면 된다. 이렇게 함수를 리턴해주면 해당 컴포넌트가 언마운트 될 때, 혹은 다음 렌더링 시 불릴 useEffect가 실행되기 이전에 이 함수가 실행된다.
헷갈리니까 예제코드를 보면서 사용법을 좀 더 익혀보자.

예제 1. 기본 사용예제

import { useState } from 'react';

function App() {
  const [num, setNum] = useState(1);
  const handleNumUpdate = () => {
    setNum(num+1);
  }

  return (
    <div>
      <button onClick = { handleNumUpdate }> update </button>
      <span> num: { num }</span>
    </div>
  );
}

export default App;

업데이트 버튼을 누르면 num라는 state를 1씩 증가시키는 페이지를 만들었다. 그러면 이제 useEffect를 사용해보자.

import { useState, useEffect } from 'react'; // import 하기 

function App() {
  const [num, setNum] = useState(1);
  const handleNumUpdate = () => {
    setNum(num+1);
  }

  // 렌더링 될 때마다 실행됨
  useEffect(()=>{
    console.log("useEffect✨");
  }![](https://velog.velcdn.com/images/hyun/post/9c79f3fc-784f-42d0-9c19-521fb0c00355/image.gif)
)

  return (
    <div>
      <button onClick = { handleNumUpdate }> update </button>
      <span> num: { num }</span>
    </div>
  );
}

export default App;

가장 기본적인 useEffect의 형태를 하나 추가해 주었다. useEffect는 컴포넌트가 렌더링될 때마다 실행된다고 했으므로 확인하기 위해 콜백 함수 내에 console.log를 넣어 주었다.

우리가 업데이트 버튼을 누를 때마다 setNum이 호출되고, num이라는 state 값이 변하므로 컴포넌트가 리렌더링 될 것이다. 함수형 컴포넌트는 state값이 변할 때마다 다시 렌더링 되기 때문이다. 즉 렌더링이 될 때마다 useEffect가 정상적으로 불려지는 것을 확인했다.

이번에는 state를 하나 더 추가해보자.

import logo from './logo.svg';
import './App.css';
import { useState, useEffect } from 'react';


function App() {
  const [num, setNum] = useState(1);
  const [text, setText] = useState('');
  
  const handleNumUpdate = () => {
    setNum(num+1);
  }

  const handleInputChange = (e) => {
    setText(e.target.value);
  }
  // 렌더링 될 때마다 실행됨
  useEffect(()=>{
    console.log("useEffect✨");
  })

  return (
    <div>
      <button onClick = { handleNumUpdate }> update </button>
      <span> num: { num }</span>  
      <br/>t
      <input type="text" value={ text } onChange={ handleInputChange }/>
      <span> { text } </span>
    </div>
  );
}

export default App;

text state를 추가해주고, input 창에 입력되는 값을 span에 바로 보여지도록 만들었다. 입력창에 무언가를 입력할 때마다 handleInputChange가 호출되고, 그 안에서 setText가 호출되어 text state가 변경될 것이다. 그러면 글자를 입력할 때마다 컴포넌트가 렌더링되고 useEffect가 불릴 것이다.

컴포넌트가 매번 렌더링 될 때마다 useEffect를 사용하니까 너무 많이 불리게 되어서, 이 안에서 무거운 작업을 하게 된다면 굉장히 부하가 많이 걸릴 것이다. 이번에는 num이 업데이트 될 때만 useEffect가 실행되도록 의존성 배열을 이용해서 조건을 걸어 볼 것이다.

import { useState, useEffect } from 'react';

function App() {
  const [num, setNum] = useState(1);
  const [text, setText] = useState('');
  
  const handleNumUpdate = () => {
    setNum(num+1);
  }

  const handleInputChange = (e) => {
    setText(e.target.value);
  }
  // num이 업데이트 될 때마다 실행됨
  useEffect(()=>{
    console.log("num이 업데이트 될 때만 실행");
  }, [num])

  return (
    <div>
      <button onClick = { handleNumUpdate }> update </button>
      <span> num: { num }</span>  
      <br/>
      <input type="text" value={ text } onChange={ handleInputChange }/>
      <span> { text } </span>
    </div>
  );
}

export default App;

useEffect의 두 번째 인자인 의존성 배열에 num을 넣어 주었다.

우리가 원하는 대로, text를 변경할 때에는 컴포넌트가 렌더링 되어도 useEffect가 호출되지 않는다. 컴포넌트가 처음 마운트 될 때, 그리고num이 변경될 때만 useEffect가 실행된다.

이번에는 useEffect의 의존성 배열에 빈 배열을 넣어보자. 위 코드의 useEffect 부분을 아래와 같이 바꿔보았다.

  // 처음 마운트 될 때만 실행됨
  useEffect(()=>{
    console.log("마운트 될 때만 실행");
  }, [])


처음 컴포넌트가 마운트 될 때만 콘솔이 찍히고 아무리 num, text state를 변경해도 반응이 없다.

예제2. Clean Up

// component/Timer.js

import React, { useEffect } from "react";
const Timer = (props) => {
    useEffect(()=>{
        const timer = setInterval(()=>{
            console.log("타이머 작동중...");
        }, 1000);
    }, [])

    return(
        <div>
            <span> 타이머입니다⏱ </span>
        </div>
    )
}   

export default Timer;

이번에는 Timer라는 컴포넌트를 만들어 보았다. 이 컴포넌트는 마운트 되면 1초마다 "타이머 작동중..."이라는 콘솔을 찍어주는 기능을 한다.

// App.js
import Timer from './component/Timer';
import { useState, useEffect } from 'react';

function App() {
  const [showTimer, setShowTimer] = useState(false);
  return (
    <> 
      {showTimer && <Timer />}
      <button onClick = { () => { setShowTimer(!showTimer) }}> { showTimer ? "타이머 끄기" : "타이머 켜기"}</button> 
    </>
  );
}

export default App;

App 컴포넌트에서는 Timer 컴포넌트를 마운트하고, 버튼을 만들어 타이머를 켜고 끌 수 있게 해주었다.

잘 작동이 된다. 그런데, 타이머 끄기 버튼을 눌러서 Timer 컴포넌트가 언마운트 되었는데도, 타이머가 꺼지지 않고 계속 작동하고 있다. 타이머를 끄기 위해서 useEffect를 clean up 해보자.

// component/Timer.js
import React, { useEffect } from "react";
const Timer = (props) => {
    useEffect(()=>{
        const timer = setInterval(()=>{
            console.log("타이머 작동중...");
        }, 1000);

        return () => {
            // clean up
            clearInterval(timer);
            console.log("Clean up	");
        }
    }, [])

    return(
        <div>
            <span> 타이머입니다⏱ </span>
        </div>
    )
}   

export default Timer;

useEffect를 clean up 하려면, useEffect의 첫 번째 인자인 콜백함수의 리턴에서 코드를 작성하면 된다. 우리가 해야 할 작업은 타이머를 끝내는 것이므로 return하는 함수 안에서 타이머를 종료해 주었다.

타이머가 잘 꺼지는 것을 확인할 수 있다.

profile
프론트엔드를 공부하고 있습니다.

0개의 댓글