글 작성자: 개발자 올라프

📝 개요

 

  • 웹 사이트를 만들다 보면 화면에 보일 수 있는 데이터를 서버에서 받아오기도 해야 하고, state가 바뀔 때마다 함수를 실행 시키거나, 이벤트 리스너를 달았다가 해제하는 등의 동작이 필요할 수 있다. 이 때 useEffect훅이 필요하며, 빈번히 사용되는 중요한 개념이므로 useEffect에 대해서 정리하는 시간을 갖는다.

 


 

React에서 함수 컴포넌트의 rendering이란 state, props 기반으로 UI요소를 그려내는 행위이다. (state, props) => UI와 같이 표현이 가능하다. 즉, rendering 결과물은 UI요소로 JSX 문법으로 무엇이 나타날지 적어둔 component이다.

 

Side Effect

컴퓨터 과학에서 함수가 결과값 이외에 다른 상태를 변경시킬 때 '부작용'이 있다고 말한다. 함수가 동작할 때 input, output 이외의 다른 값을 조작한다면, 이 함수에는 side effect(부수효과)가 있다고 표현한다. 함수 컴포넌트에서의 side effect는 렌더링이 아닌 외부 세계에 영향을 주는 어떠한 행위이다.

대표적으로 Data FetchingDOM에 접근(ex. EventListener등록), 구독(ex. setInterval)등이 있다. 이들은 컴포넌트에서 꼭 필요한 대표적인 side effect에 해당한다.

// input을 받아 output을 내는 본질적인 함수로 side effect가 없는 순수함수
const sumOne = (num) => {
  return num+1;
}

// console창은 외부의 값이다. 외부 요소에 접근, 읽기, 수정은 모두 side effect에 해당
const sumOne = (num) => {
  console.log("num = ", num);
  return num+1;
}

// 당연히 함수 내에 외부의 값 plusNum을 읽었으니 side effect에 해당
const plusNum = 30;
const sumNum = (num) => {
  num + plusNum;
}

 


 

useEffect

side effect들을 함수의 body(render)에서 실행시키면 안된다. 함수 컴포넌트의 리턴 값은 UI 요소이며, state, props 변화가 있을 때마다 함수가 실행된다. 즉, 매 렌더링마다 함수 body에 있는 로직이 실행된다는 의미이다. 렌더링과 무관한 로직이 렌더링 과정에서 실행되면 렌더링 자체에 영향을 주기 때문에 성능에 악영향을 끼친다.

 

side effect를 일으키기 적당한 장소로 useEffecthook을 제공한다. useEffect는 side effect를 렌더링한 이후에 동작한다. 어려운 연산이나 서버에서 데이터 가져오는 작업 등을 useEffect에 작성하면 효율적으로 웹 페이지를 보여줄 수 있다.

 


 

Rendering Cycle with useLayoutEffect

useEffect hook이 등장하기 이전에는 클래스함수로 기존 컴포넌트 Lifecycle에 간섭하려면 아래와 같은 코드를 사용했다.

class lifecycle extends React.Component {
	componentDidMount() {
    	// 컴포넌트 페이지 장착시
    }
    componentDidUpdate() {
    	// 컴포넌트 업데이트시
    }
    componentWillUnmount() {
    	// 컴포넌트가 필요 없을시
    }
}

 

다음으로 useEffect hook 사용 방법에 대해서 알아보자.

// useEffect(실행시킬 동작, [타이밍])

// 1. 매 Rendering 마다 Side Effect가 실행되어야 하는 경우
useEffect(() => {
  // SIDE EFFECT
})

// Side Effect가 첫 Rendering 이후 한 번 실행되고, 이후 특정 값(value)의 업데이트(state 변경) 감지했을 때 실행되어야 하는 경우
useEffect(() => {
  // SIDE Effect
}, [value])

// Side Effect가 첫 Rendering 이후 한 번 실행되고, 이후 어떤 값의 업데이트도 감지하지 않도록 해야 하는 경우
useEffect(() => {
  // SIDE EFFECT
}, [])

// 추가
useEffect(() => {
    return() => {
      // useEffect 동작 이전에 실행되는 부분 작성
    }
})
  1. 컴포넌트가 렌더링 된다. 최초로 진행되는 렌더링은 브라우저에 처음으로 이 컴포넌트가 보여졌다는 의미로 mount라고 표현한다.
  2. useEffect 첫 번째 인자로 넘겨준 함수 callback함수가 실행된다 => side effect
  3. state나 props가 변경된 경우, re-render된다.
  4. useEffect는 두 번째 인자에 들어 있는 의존성 배열을 체크한다. 만약 두 번째 인자에 아무 값도 넘기지 않거나, 배열에 들어있는 값 중 하나라도 업데이트 됐다면 첫 번째 인자로 넘긴 callback이 실행된다. 반대로 빈 배열이라면 아무런 동작을 하지 않는다.
  5. 만약 앞서 일으킨 effect에서 state나 props를 변경시켰다면 re-render을 반복한다.
  6. 컴포넌트가 필요 없어지면 화면에서 사라진다. 컴포넌트가 브라우저 화면에서 사라졌다는 의미로 unmount라고 표현한다.

 


 

Cleanup Effect

useEffect(() => {
  function handleScroll() {
    console.log(window.scrollY)
  }
  document.addEventListener("scroll", handleScroll)
  return () => {
    document.removeEventListener("scroll", handleScroll)
  }
}, [])

앞서 발생한 side effect를 정리할 필요가 있을 때 사용한다. 해당 이벤트를 구현하는 페이지를 벗어나면 이벤트는 더 이상 필요 없으며, 이 경우 발생시킨 effect를 정리해야 하는데 이 때마다 cleanup effect를 일으킬 수 있도록 useEffect내에 해당 로직을 정리하는 동작을 정의하면 된다.

 


 

순서 이해하기

const App = () => {
  const [count, setCount] = useState(0);

  console.log("render", count);

  useEffect(() => {
    console.log("useEffect Callback", count);
    return () => {
      console.log("cleanUp", count);
    });
  }, [count]);

  return <div onClick={() => setCount(count + 1)}>CLICK</div>;
};

export default Foo;

/*
render, 0
useEffect Callback, 0

<클릭 후>

render, 1
cleanUp, 0
useEffect Callback, 1
*/