[TIL #26] - useEffect

Sean yang~~·2022년 9월 7일
0

React

목록 보기
7/11
post-thumbnail

Side Effect의 개념

프로그래밍에서는 side Effect 라는 용어가 있습니다. === 부작용, 일상생활에서 접하는 부작용이라는 단어의 어감은 다소 부정적입니다. 부작용은 부수효과라고도 표현할 수 있ㅅ브니다. 부수효과란 말을 해석해보면 주요한 효과에 따라서 발생하는 효과라고 할 수 있습니다.

프로그래밍에서의 부작용은 무엇일까요? 프로그래밍에서의 부작용은 코드가 의도한 주된 효과 외에 추가적으로 발생하는 효과라고 할 수 있습니다. 프로그램을 이루는 가장 작은 단위인 함수에서 쓰이는 용어입니다.

함수의 주된 목적은 Input을 받아서 output을 산출하는 것입니다. 따라서, 함수의 부작용이란 함수의 목적인 Input을 받아서 output을 산출하는 것 이외의 모든 행위를 의미합니다.

Side Effect의 예시

sum 함수는 x라는 input을 받아서 x + 1이라는 output을 산출하는 함수입니다. 이 함수는 input을 받아서 output을 내는 행위 외에 다른 행위를 하지 않으므로 side effect가 없다하고 할 수 있습니다. 이처럼 side effect가 없는 함수를 순수 함수라고 부릅니다.

외부의 상태를 읽어오기

sum 함수는 x라는 input을 받아서 x + numreturn하고 있습니다. 이 함수는 side effect를 가지고 있습니다. 함수 내부의 값이 아닌 외부의 값인 num을 읽어오고 있기 때문입니다. 이처험 함수가 함수 내부의 값(local state)를 제외한 나머지 값(non local state)들을 읽어올 때 side effect가 있다고 표현할 수 있습니다.

외부의 상태를 변경시키기

이 함수는 side effect를 가지고 있습니다. 왜냐, 함수가 함수 내부에 있는 값이 아닌, 외부의 값(num)을 변경시키고 있기 때문입니다. 이렇게 함수가 함수 외부의 값을 변경시킨다면 “side effect 가 발생했다" 라고 표현할 수 있습니다.

위 두 함수는 side effeect를 발생시키고 있습니다. 외부의 값이라고 하면 외부에 있는 변수만을 생각하기 쉽지만, DOM을 조작하고, console에 특정문자를 출력하는 행위 또한 함수 외부에 존재하는 DOM과 console의 상태를 변경시키는 것이기에 side effect가 발생한다고 할 수 있습니다.

즉, 프로그래민에서 side effect는 함수가 input을 받아서 output을 산출하는 과정에서

  1. 외부의 값을 읽어오는 행위
  2. 외부의 값을 변경하는 행위를 의미합니다.

그렇기 떄문에, side effect는 기피해야하는 대상이다. 왜냐하면 side effect가 있는 함수는 동작 결과를 예측하기 쉽지 않기 때문입니다. const sum = (x) → x + 1이란 함수는 항상 우리가 1을 인자로 넣으면 2가 return 될것이라 예측할 수 있지만, const sum = (x) ⇒ x + num이란 함수는 num이란 값이 어떻게 변할지 모르기 떄문에 함수의 결과를 예측하기 어려워집니다. 따라서 side effect가 있는 함수들은 유지 보수 할 때 개발자에게 어려움을 겪게 만들 수 있습니다.

프로그램이 외부로 그 어떤 값도 출력하지 않는다면 그 프로그램은 의미가 없고, 또한 데이터를 어딘가에 저장해두고 저장되어 있는 값을 읽어오는 행위는 프로그래밍에서 있어서 필수불가결한 요소이기 떄문입니다. 개발자들은 side effect를 최소화 하면서 프로그램을 설계하되, side effect가 필요한 경우에는 그것을 반드시 통제 가능하게 만들어서 side effect가 프로그램의 유지보수에 악영향을 주지 않도록 주의를 기울여야 합니다.

React에서의 Side Effect

함수 컴포넌트에서의 input과 output

React에서 rendering 이란, state,props를 기반으로 UI 요소를 그려내는 행위입니다. 함수 컴포넌트에서의 input과 output은 무엇일까요? 함수 컴포넌트는 state와 props를 통해서 JSX를 만들어내는 것이 본질적인 역할입니다. 함수 컴포넌트의 input은 state와 props 이며 output은 JSX라고 표현할 수 있습니다.

React에서의 함수 컴포넌트는 state, props를 가지고 JSX 를 만들어내는 함수이며 이를 간단히 도식화하자면 (state,props) ⇒ JSX

  • state는 함수 컴포넌트에 인자로 전달되는 값이 아니라, 컴포넌트 내부에서 useState hook을 통해서 가져오는 것이지만 개념상으로는 외부에서 가져오는 값이다.

함수 컴포넌트에서의 Side Effect

함수 컴포넌트에서의 side effect는 대표적으로 3가지가 있습니다.

useEffect

위와 같이 렌더링 단계(UI를 만들어내는 과정)에서 side effect를 발생시키게 되면 두 가지 문제가 발생합니다.

  1. side effect가 렌더링을 blocking 합니다.
  2. 매 렌더링 마다 side effect가 수행됩니다.

side effect가 렌더링을 blocking

위 코드는 사이드 이펙트를 함수 컴포넌트 본문 안에서 실행시킵니다.

기본적으로 코드는 위에서 아래 방향으로 순차적으로 실행됩니다. 따라서 App 함수 컴포넌트는 doSideEffect() 동작이 끝날 떄까지 JSX를 리턴하는 코드로 넘어가지 않습니다. 컴포넌트가 JSX를 return 하기 전까지는 UI가 브라우저상에 렌더링 되지 않기 떄문에 결국 사이드 이펙트가 긑나지 건짜기 렌더링을 하지 못하고 멈춰있게 됩니다.

즉, 사용자가 UI 업데이트되는 것을 보기까지 오랜 시간이 소요된다는 것입니다. 이는 곧 사용자에세 좋지 못한 사용자 경험을 제공한다는 의미.

매 렌더링마다 side effect가 수행(always trigger)

특정한 side effect들은 매번 실행될 필요가 없을 수도 있습니다.

예를들어, 인스타그램에서 피드에 대한 정보를 받아서 피드 리스트를 보여주는 화면있을 경우, 피드 리스트를 보여주기 위해서는 최초에 피드 데이터들을 가져오는 (Data Fetching) side effect가 필요합니다. 하지만 외부에서 가져오는 side effecr는 매 렌더링마다 수행될 필요는 없고, 오히려 매번 수행한다면 비효율적이다.

리액트에서 함수 컴포넌트가 리렌더링 된다는 것은 곧 함수 컴포넌트를 다시 한번 호출한다는 뜻입니다.

(리액트는 컴포넌트의 state나 props가 변하면 자동으로 해당 함수 컴포넌트를 다시 호출하면서 리렌더링을 수행해 줍니다.)

즉, App이라는 함수가 다시 호출되고 그렇다면 다시 함수 내부의 코드를 위에서 아래 방향으로 순차적으로 실행시킵니다. 그 말은 다시한번 getFeeds() side effect를 발생키니다는 의미입니다.

결과적으로,

  1. 렌더링을 Blocking 하지 않기 위해서 렌더링이 모두 다 완료되고 난 후 실행할 수 있어야 한다.
  2. 매 렌더링마다 실행되는 것이 아니라 내가 원할 떄만 조건부로 실행할 수 있어야 한다.

useEffect 사용법

useEffect는 React에서 side effect를 편리하고 안전하게 발생시킬 수 있게 도와주는 hook입니다.

useEffect(콜백 함수);

useEffect는 함수이고, 매개변수로 콜백함수를 가집니다. useEffect에 인자로 전달하는 콜백 함수에서 특정한 side effect를 수행시킬 수 있습니다.

1.

const App = () => {
	doSdieEffect(); // side effect

	return <h1> Hello, Wecoder </h1>; // 렌더링
};

> 렌더링을 blocking 하기에 좋지 않다.

===============================================

2. 위의 문제를 해결하기 위해 useEffect를 사용해보자

import { useEffect } from 'react';

const App = () => {

	useEffect(doSideEffect);

	return <h1> Hello, Wecoder </h1>; 
};

side effect를 발생시키는 함수를 바로 호출하는 것이 아니다 useEffect의 인자로 전달했다. 위와 같이 useEffecr의 인자로 전달된 콜백 함수는 곧바로 호출되는 것이 아니라 모든 렌더링이 완료된 후에 호출된다.

조건부로 side Effect 발생시키기

side effect를 특정 조건이 충족할 떄만 발생시키는 방법

useEffect(콜백 함수, 의존성 배열);

use effect는 콜백 함수 외에 의존성 배열(dependencyArray) 이라는 두번째 매개변수를 가집니다. 이 배열은 side effect의 발생여부를 결정짓는 조건입니다.

첫번째 렌더링 이후에는 무조건 useEffect에 전달된 콜백함수를 호출하고 다름 렌더링부터는 아래의 조건에 따라 동작합니다.

  1. 의존성 배열이 전달되지 않았다면 매 렌더링마다 콜백함수를 호출한다.
  2. 의존성 배열이 전달되었다면 의존성 배열의 값을 검사한다.
    • 의존성 배열에 있는 값 중 하나라도 이전 렌더링과 비교했을 떄 달라졌다면 콜백 함수를 호출한다.
    • 의존성 배열에 있는 값이 이전 렌더링과 비교했을 떄 모두 다 같다면 콜백함수를 호출하지 않는다.

즉, useEffect에 서 첫 번째 인자인 콜백 함수는 실행시킬 동작을 결정하고 두 번째 인자인 의존성 배열은 실행시킬 타이밍을 결정짓는다고 할 수 있습니다.

우리가 Data Fetching을 최초 한 번만 실행하면 될 경우에는 useEffect를 활용하면서 의존성 배열에 빈 배열을 넣어주면 최초 한 번만 데이터를 가져오는 side effect를 발생시키고, 그 이후에는 리렌더링이 되더라고 다시 데이터를 가져오지 않도록 만들 수 있습니다.

Rendering & Effect Cycle

  1. 컴포넌트가 렌더링 된다. (최초로 진행되는 렌더링은 브라우저에 처음으로 이 컴포넌트가 보였다는 의미로 mount 라고 표현합니다.)
  2. useEffect 첫 번째 인자로 넘겨준 콜백 함수가 호출된다.(side Effect)
  3. 컴포넌트의 state 또는 props가 변경되었을 경우 리렌더링이 발생한다. (update)
  4. useEffecr는 두 번째 인자에 들어있는 의존성 배열을 확인한다.
    1. 만약 의존성 배열이 전달되지 않았거나 / 의존성 배열 내부의 값 중 이전 렌더링과 비교했을 때 변경된 값이 하나라도 있다면 첫 번재 인자로 넘겨준 콜백 함수가 호출된다.(Side Effect)
    2. 의존성 배열 내부의 값 중 이전 렌더링과 비교했을 때 변경된 값이 없다면 콜백 함수를 호출하지 않는다.
    3. state 또는 propts가 변경된다면 3~4의 과정을 반복
  5. 컴포넌트가 더 이상 필요없어지면 화면에서 사라진다.

(컴포텉느가 브라우저의 화면에서 사라졌다는 의미로 unmount라고 표현한다.)

clean Up effect

clean up은 무언가를 정히라고 치운다는 의미힙니다. useEffect hook은 side effect를 clean up 해주는 기능 또한 가지고 있습니다.

Clean Up의 필요성

위의 side effect는 cleanup이 필요합니다. 이 side effect는 setInterval 함수를 이용해서 100ms마다 countTime 함수가 호출되도록 하고 있습니다. useEffect의 의존성 배열에 빈 배열이 전달되었으므로 첫 번쨰 렌더링 이후에 side effect가 실행됩니다. 그런데 이 side effect를 clean up 해주지 않는다면 컴포넌트가 unmount되는 경우 들 setInterval 을 통한 구독이 필요 없어진 상황에서도 계속해서 콘솔이 출력되고 있을 것입니다.

이번에는 useEffect에 의존성 배열을 전달하지 않았습니다. 따라서, 이 side effect는 매 렌더링마다 실행됩니다. side effect가 하는일을 살펴보자

  1. document.getElementById 메서드를 통해서 consoleButton 이란 ID를 가진 요소를 가져와서 button 변수에 할당한다.
  2. printConsole 함수를 선언한다 이 함수는 console.log를 출력하는 함수이다.
  3. button에 click 이벤트가 발생할 떄마다 printConsole이 실행되도록 buttondp eventListner로 등록한다.

이 side effect는 매 렌더링마다 실행되기에 렌더링이 될 떄마다 button에 eventListener가 추가됩니다. 즉 계속 중첩되고 있다는 의미입니다.

불필요하게 계속해서 side effecr가 남아있어서 비효율적으로 작동할 수 있고, 프로그램의 동작이 의도한 대로 되지 않을 수도 있기 때문에 지속적으로 남아있는 side effect는 반드시 clean up을 해줘야 합니다.

Clean Up 방법

useEffect에서 side effectFMF cleanup 하기 위해서는 useEffecr에 전달한 콜백 함수에서 clean up을 하는 함수를 리턴하면 됩니다.

위와 같이 발생시킨 side effect를 상쇄하기 위한 함수를 만든 뒤 그 함수를 return해주면 됩니다. addEventListener로 등록한 eventListnerremoveEventListener 함수를 통해서 제거할 수 있기 때문에 해당 동작을 하는 함수(clean up 함수)를 만든 뒤 콜백 함수 내에서 clean up 함수를 리턴해줬습니다. clean up 함수를 return만 해준다면 clean up 함수를 적절한 시점에 호출해주는 일은 useEffect가 알아서 처리해줍니다.

useEffect는 clean up 함수를 두가지 경주에 호출해줍니다.

  1. 다음 side Effect를 발생시키기전
  2. 컴포넌트가 unmount 될때

위 두 가지 경우가 발생하면 useEffect는 clean up 함수를 호출해 줍니다.

동작을 스텝별로 설명 해보자

  1. useEffect에 의존성 배열을 전달하지 않았기 때문에 해당 effect는 매 렌더링마다 실행된다.
  2. 리렌더링이 발생해서 useEffect가 다시 호출되는 상황이 발생한다.
    1. clean up 함수를 리턴해줬기 때문에 clean up 함수가 호출된다.
    2. clean up 함수가 호출된 뒤 effect가 발생된다.
    3. 리렌더링이 발생하면 a~b의 과정이 반복된다.
  3. 컴포넌트가 unmount 되면 clean up 함수가 호출된다.
profile
나는 프론트엔드 개발자다!

0개의 댓글