프로그래밍에서는 side Effect
라는 용어가 있습니다. === 부작용
, 일상생활에서 접하는 부작용이라는 단어의 어감은 다소 부정적입니다. 부작용은 부수효과라고도 표현할 수 있ㅅ브니다. 부수효과란 말을 해석해보면 주요한 효과에 따라서 발생하는 효과라고 할 수 있습니다.
프로그래밍에서의 부작용
은 무엇일까요? 프로그래밍에서의 부작용은 코드가 의도한 주된 효과 외에 추가적으로 발생하는 효과라고 할 수 있습니다. 프로그램을 이루는 가장 작은 단위인 함수에서 쓰이는 용어입니다.
함수의 주된 목적은 Input을 받아서 output을 산출하는 것입니다. 따라서, 함수의 부작용이란 함수의 목적인 Input을 받아서 output을 산출하는 것 이외의 모든 행위를 의미합니다.
sum 함수는 x라는 input을 받아서 x + 1이라는 output을 산출하는 함수입니다. 이 함수는 input을 받아서 output을 내는 행위 외에 다른 행위를 하지 않으므로 side effect가 없다하고 할 수 있습니다. 이처럼 side effect가 없는 함수를 순수 함수
라고 부릅니다.
sum
함수는 x라는 input
을 받아서 x + num
을 return
하고 있습니다. 이 함수는 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을 산출하는 과정에서
그렇기 떄문에, 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에서 rendering 이란, state,props를 기반으로 UI 요소를 그려내는 행위입니다. 함수 컴포넌트에서의 input과 output은 무엇일까요? 함수 컴포넌트는 state와 props를 통해서 JSX를 만들어내는 것이 본질적인 역할입니다. 함수 컴포넌트의 input은 state와 props 이며 output은 JSX라고 표현할 수 있습니다.
React에서의 함수 컴포넌트는 state, props를 가지고 JSX 를 만들어내는 함수이며 이를 간단히 도식화하자면 (state,props) ⇒ JSX
함수 컴포넌트에서의 side effect는 대표적으로 3가지가 있습니다.
위와 같이 렌더링 단계(UI를 만들어내는 과정)에서 side effect를 발생시키게 되면 두 가지 문제가 발생합니다.
위 코드는 사이드 이펙트를 함수 컴포넌트 본문 안에서 실행시킵니다.
기본적으로 코드는 위에서 아래 방향으로 순차적으로 실행됩니다. 따라서 App 함수 컴포넌트는 doSideEffect() 동작이 끝날 떄까지 JSX를 리턴하는 코드로 넘어가지 않습니다. 컴포넌트가 JSX를 return 하기 전까지는 UI가 브라우저상에 렌더링 되지 않기 떄문에 결국 사이드 이펙트가 긑나지 건짜기 렌더링을 하지 못하고 멈춰있게 됩니다.
즉, 사용자가 UI 업데이트되는 것을 보기까지 오랜 시간이 소요된다는 것입니다. 이는 곧 사용자에세 좋지 못한 사용자 경험을 제공한다는 의미.
특정한 side effect들은 매번 실행될 필요가 없을 수도 있습니다.
예를들어, 인스타그램에서 피드에 대한 정보를 받아서 피드 리스트를 보여주는 화면있을 경우, 피드 리스트를 보여주기 위해서는 최초에 피드 데이터들을 가져오는 (Data Fetching) side effect가 필요합니다. 하지만 외부에서 가져오는 side effecr는 매 렌더링마다 수행될 필요는 없고, 오히려 매번 수행한다면 비효율적이다.
리액트에서 함수 컴포넌트가 리렌더링 된다는 것은 곧 함수 컴포넌트를 다시 한번 호출한다는 뜻입니다.
(리액트는 컴포넌트의 state나 props가 변하면 자동으로 해당 함수 컴포넌트를 다시 호출하면서 리렌더링을 수행해 줍니다.)
즉, App이라는 함수가 다시 호출되고 그렇다면 다시 함수 내부의 코드를 위에서 아래 방향으로 순차적으로 실행시킵니다. 그 말은 다시한번 getFeeds()
side effect를 발생키니다는 의미입니다.
결과적으로,
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를 특정 조건이 충족할 떄만 발생시키는 방법
useEffect(콜백 함수, 의존성 배열);
use effect는 콜백 함수 외에 의존성 배열(dependencyArray) 이라는 두번째 매개변수를 가집니다. 이 배열은 side effect의 발생여부를 결정짓는 조건입니다.
첫번째 렌더링 이후에는 무조건 useEffect에 전달된 콜백함수를 호출하고 다름 렌더링부터는 아래의 조건에 따라 동작합니다.
즉, useEffect에 서 첫 번째 인자인 콜백 함수는 실행시킬 동작을 결정하고 두 번째 인자인 의존성 배열은 실행시킬 타이밍을 결정짓는다고 할 수 있습니다.
우리가 Data Fetching을 최초 한 번만 실행하면 될 경우에는 useEffect를 활용하면서 의존성 배열에 빈 배열을 넣어주면 최초 한 번만 데이터를 가져오는 side effect를 발생시키고, 그 이후에는 리렌더링이 되더라고 다시 데이터를 가져오지 않도록 만들 수 있습니다.
mount
라고 표현합니다.)(update)
(컴포텉느가 브라우저의 화면에서 사라졌다는 의미로 unmount
라고 표현한다.)
clean up은 무언가를 정히라고 치운다는 의미힙니다. useEffect hook은 side effect를 clean up 해주는 기능 또한 가지고 있습니다.
위의 side effect는 cleanup이 필요합니다. 이 side effect는 setInterval
함수를 이용해서 100ms마다 countTime
함수가 호출되도록 하고 있습니다. useEffect의 의존성 배열에 빈 배열이 전달되었으므로 첫 번쨰 렌더링 이후에 side effect가 실행됩니다. 그런데 이 side effect를 clean up 해주지 않는다면 컴포넌트가 unmount되는 경우 들 setInterval
을 통한 구독이 필요 없어진 상황에서도 계속해서 콘솔이 출력되고 있을 것입니다.
이번에는 useEffect에 의존성 배열을 전달하지 않았습니다. 따라서, 이 side effect는 매 렌더링마다 실행됩니다. side effect가 하는일을 살펴보자
이 side effect는 매 렌더링마다 실행되기에 렌더링이 될 떄마다 button에 eventListener가 추가됩니다. 즉 계속 중첩되고 있다는 의미입니다.
불필요하게 계속해서 side effecr가 남아있어서 비효율적으로 작동할 수 있고, 프로그램의 동작이 의도한 대로 되지 않을 수도 있기 때문에 지속적으로 남아있는 side effect는 반드시 clean up을 해줘야 합니다.
useEffect에서 side effectFMF cleanup 하기 위해서는 useEffecr에 전달한 콜백 함수에서 clean up을 하는 함수를 리턴하면 됩니다.
위와 같이 발생시킨 side effect를 상쇄하기 위한 함수를 만든 뒤 그 함수를 return해주면 됩니다. addEventListener
로 등록한 eventListner
는 removeEventListener
함수를 통해서 제거할 수 있기 때문에 해당 동작을 하는 함수(clean up 함수)를 만든 뒤 콜백 함수 내에서 clean up 함수를 리턴해줬습니다. clean up 함수를 return만 해준다면 clean up 함수를 적절한 시점에 호출해주는 일은 useEffect가 알아서 처리해줍니다.
useEffect는 clean up 함수를 두가지 경주에 호출해줍니다.
위 두 가지 경우가 발생하면 useEffect는 clean up 함수를 호출해 줍니다.
동작을 스텝별로 설명 해보자