본문 바로가기
Front-end

[React] useEffect 란?

by 질서정연_ 2022. 11. 3.

useEffect 란 ?

 

 

 

useEffect 란?

useEffect(callback, [dependencies])

Effect 란?

useEffect 는 왜 useEffect 라고 불리는걸까?

effect 란 무엇일까? 

effect는 함수형 프로그래밍 용어인 "side effect" 를 의미한다. 

side effect 가 뭔지 이해하기 위해 먼저 pure function 에 대해 이해 해 보자

대부분의 react component는 pure function 이 되려고 한다. 

react component 를 함수라고 생각하는게 이상하겠지만 react component 는 함수다!

대부분의 react component는 pure function 이며 input을 받고 예측 가능한 JSX output을 만들어낸다. 

 

export default function App() {
  return <User name="John Doe" />   
}
  
function User(props) {
  return <h1>{props.name}</h1>; // John Doe
}

여기서 User Component는 name props 를 받고 항상 props와 같은 output을 낸다.

이런걸 pure function 이라고 한다!

pure function 은 예측 가능하고, 신뢰가 가고, 테스트하기 쉽다. 

pure function 은 우리가 component의 side effect 를 필요로 할 때와 비교된다.. 

 

리액트에서 side effect 란? 

side effect 는 예측할 수 없다. side effect 는 component 외부에서 발생하기 때문이다. 

리액트 컴포넌트 외부에서 무언가 하기를 원할 때 side effect 를 수행한다. 

side effect 를 수행하는것은 우리에게 예측 가능하지 않은 결과를 준다. 

 

우리가 서버에게 실패 한 request data 를 줬다고 생각 해 보자. 

서버는 500 상태코드를 반환 할거다. 

 

일반적인 side effect 는 다음과 같다

  • backend server에 API request 요청
  • 브라우저 API와 상호작용 (document나 window를 직접적으로 쓸 때)
  • 예측 할 수 없는 timing function 을 사용 할 때 (setTimeout 혹은 setInterval)

순수한 React 구성 요소에서 이러한 side effects 수행을 처리하기 위해 useEffect 가 존재한다.

 

즉 결과를 예측 할 수 없는 pure 하지 못한 상황 = side effect 라고 생각하면 되겠다.

이 결과를 예측 할 수 없는 side effect 를 제어하기 위해 사용하는 것이 useEffect 이다!

 

예시를 하나 보자.

 

function User({ name }) {
  document.title = name; 
  // This is a side effect. Don't do this in the component body!
    
  return <h1>{name}</h1>;   
}

만약 side effect 를 component body 안에서 직접 수행하려 한다면 이 결과는 react 컴포넌트가 렌더링 될 때 수행된다. 

side effect 는 rendering process 와 분리되어야한다. 

만약 우리가 side effect 를 수행해야한다면, 이건 엄격하게 component 가 rendering  된 후 수행되어야한다.

 

요약하면, useEffect 는 component외부와 상호작용 할 수 있게 해 주는 도구이며

컴포넌트 내부의 렌더링과 퍼포먼스에는 영향을 미치지 않게 해준다.

 

useEffect 사용법

useEffect의 형태는 다음과 같다 

// 1. import useEffect
import { useEffect } from 'react';

function MyComponent() {
  // 2. call it above the returned JSX  
  // 3. pass two arguments to it: a function and an array
  useEffect(() => {}, []);
  
  // return ...
}

단계별로 보면,

  1. useEffect 를 import 한다.
  2. 리턴 될 JSX 위에서 호출한다.
  3. function 과 array. 두 arguments를 넘겨준다.
import { useEffect } from 'react';

function User({ name }) {
  useEffect(() => {
    document.title = name;
  }, [name]);
    
  return <h1>{name}</h1>;   
}

 

useEffect에 넘겨질 function 은 callback function 이어야한다. 

component가 render 된 후에 불려질 것이기 때문이다 ~

이 callback function 에서, 우리는 우리가 원하는 side effect 를 수행한다. 

 

두번째 argument는 array이다. 이 array는 side effect가 의존하는 모든 값들의 집합니다.

 

위의 예시에서 document.title 이 외부에서 오는 name에 의해 변한다. 그래서 name 을 dependencies array 에 넣어 준 것이다.

 

이 array가 하는 것은 value가 render 될 때 변하는지 확인하고 지켜본다.

만약 value가 render시에 변했다면, useEffect 에게 준 callback function 이 다시 시행된다. 

 

말이 된다 !!! 

name이 바뀌면 우리의 document.title 도 바뀌어야하니까.. 다시 시행 되어야 함 !

 

useEffect 를 쓸 때 자주 하는 실수 

만약 useEffect에게 dependencies array 를 제공하지 않고 callback 만 제공했다면.. 이 callback은 매 렌더때 마다 수행된다!

dependencies 가 제공되지 않을 때 

import {useEffect} from 'react';

function MyComponent(){
	useEffect(()=>{
    	// Runs after EVERY rendering
    });
}

렌더링 순간 마다 callback 이 실행된다. 

이건 state를 업데이트 하려고 할 때 문제가 될 수 있다. 

만약 dependencies array를 제공하는 것을 잊었다면 , 그리고 state가 update될 때 local state 를 setting 되게 했다면

react 의 default 행동은 component를 rerender 하는 것이다. 

useEffect 가 dependencies array 없이 매 렌더링 때 마다 수행된다면 우리는 무한 loop를 갖게 된다.. 

 

function MyComponent() {
  const [data, setData] = useState([])  
    
  useEffect(() => {
    fetchData().then(myData => setData(myData))
    // Error! useEffect runs after every render without the dependencies array, causing infinite loop
  }); 
}

 

first render가 되고 나서 , useEffect는 수행된다. state는 update되고 state가 update되면 re-render가 된다.

어..? re-render가 되고나면 또 useEffect를 수행하고 또 state가 update되고 또 re-render가 되고..... 

무한대로 반복되게 되는구나!

이걸 무한 loop 라고 부르고 이건 우리 어플리케이션을 박살내게 된다 ㅜ_ㅜ

 

만약 useEffect 안에서 state를 update 한다면 빈 array를 dependencies로 제공하는 것을 잊지 말아야한다 !

dependencies로 빈 Array 를 제공하자 

import { useEffect } from 'react';

function MyComponent(){
	useEffect(()=>{
    	// Runs ONCE after initial rendering
    }, []);
}

empty array 를 제공하면 컴포넌트가 맨 처음 렌더 됐을 때 effect function 을 수행하게 된다.

렌더링 이후 한번만 callback 이 실행된다. 

 

이 경우는 일반적으로 data를 fetch 해 올 때 많이 사용된다. 

function MyComponent() {
  const [data, setData] = useState([])  
    
  useEffect(() => {
    fetchData().then(myData => setData(myData))
    // Correct! Runs once after render with empty array
  }, []); 
   
  return <ul>{data.map(item => <li key={item}>{item}</li>)}</ul>
}

data를 한번만 fetch 하기 원한다면, [] 빈 array 를 넣어주자 !

 

props 혹은 state value들일 때 

import { useEffect, useState } from 'react';

function MyComponent({prop}){
	const [state, setState] = useState('');
    useEffect(()=>{
    	// Runs ONCE after initial rendering
        // and after every rendering ONLY IF 'prop' or 'state' changes
    }, [prop, state]);
}

처음 렌더링 될 때 그리고 prop 혹은 state 가 변하는 순간마다 callback 이 실행된다. 

특정 props나 state가 바뀌었을 때 수행하고싶은 함수가 있다면 dependencies에 그 props나 state를 넣어주자.

dependencie 로는 주로 빈 배열을 넣거나 prop 혹은 state를 넣는다.

 

useEffect에서 Cleanup function 이란?  

때로 우리의 side effect 는 수행되지 않아야 할 때가 있다. 

예를 들어 setInterval 함수를 사용해서 카운트다운 타이머를 가지고 있다면, interval 은 우리가 clearInterval 함수를 쓰지 않는 한 멈추지 않을거다. 

 

다른 예시를 보자. webSockets 을 구독 할 때, 구독은 우리가 더이상 그들을 사용하고 싶지 않을 때 구독 해제가 필요하다.

그리고 이것은 cleanup 함수가 하는 일이다.

 

만약 우리가 setInterval을 사용해 state를 setting했다면,  컴포넌트가 unmount되고 더이상 사용되지 않을 때  side effect는 cleaned up 되지 않는다. 

component upmount 시 state는 component와 함꼐 없어지지만 setInterval 함수는 계속 실행되게 된다.

 

function Timer() {
  const [time, setTime] = useState(0);
    
  useEffect(() => {
    setInterval(() => setTime(1), 1000); 
    // counts up 1 every second
    // we need to stop using setInterval when component unmounts
  }, []);
}

 

위의 예시에서 컴포넌트가 destroy 되어 질 때, setInterval은 계속 수행되며 더 이상 존재하지 않는 time state를 변화시키려고 계속 시도한다. 이것이 Memory leak. 메모리 누수 라고 불리는 에러이다.

 

cleanup 함수를 쓰기위해, useEffect 안에서 함수를 리턴 해야한다. 

 

function Timer() {
  const [time, setTime] = useState(0);
    
  useEffect(() => {
    let interval = setInterval(() => setTime(1), 1000); 

    return () => {
      // setInterval cleared when component unmounts
      clearInterval(interval);
    }
  }, []);
}

 

cleanup 함수는 컴포넌트가 unmount 되어 질 때 호출된다. 

컴포넌트가 unmount되는 일반적인 예제는 새로운 페이지로 가거나 새로운 route로 가거나 할 때 이다. 

컴포넌트가 unmount 될 때 cleanup function 이 실행된다. 

interval이 사라지고나면 우리는 더이상 에러가 발생하지 않는다 ~

 

side effect cleanup은 모든 케이스에 요구되지는 않는다. 

컴포넌트가 unmount 될 때 반복되는 사이드 이펙트를 멈추고 싶을 때 사용하면 된다 ! 

 

 

참조 

https://dmitripavlutin.com/react-useeffect-explanation/

 

A Simple Explanation of React.useEffect()

useEffect() hook executes side-effects in React components.

dmitripavlutin.com

https://www.freecodecamp.org/news/react-useeffect-absolute-beginners/

 

The React useEffect Hook for Absolute Beginners

If you have trouble understanding the useEffect hook, you're not alone. Beginners and experienced developers alike find it to be one of the trickiest hooks to understand, because it requires understanding a few unfamiliar programming concepts. In this quic

www.freecodecamp.org

 

 

그럼 모두 즐코 ~ 🦄

댓글