JIGGAG

리액트 메모이제이션된 컴포넌트를 재활용하여 뷰를 그린다

2019년 12월 12일

리액트?

리액트는 A, B, C, D라는 컴포넌트들을 가져다가 A-B-C 또는 A-D-B 이런식으로 활용하여 를 그리는 라이브러리이다. 각각의 컴포넌트들이 가지고 있는 상태들이 변경되면 해당 컴포넌트가 다시 그려지고 그 컴포넌트를 그리고 있던 화면도 다시 그려지게 된다. 이러한 과정에서 리액트를 잘 활용하고자 한다면 컴포넌트와 상태를 관리하는 것에 포인트를 두어야한다고 생각하게 되었다.

메모이제이션 - useCallback

1. 상태가 변경되었다
2. 컴포넌트가 변경된다
3. 화면이 다시 그려진다

상태가 변경(1)되면 컴포넌트가 다시 그려진다(2). 이때 클래스형 컴포넌트라면 render(3)만 다시 호출되지만 함수형 컴포넌트라면 내부에 선언된 함수들을 모두 다시 생성하고 화면에 그리는(3) 비효율적인 일이 발생한다. 상태가 변하기 전후로 함수가 가지고 있는 값은 항상 동일하다면 상태가 변할때 함수를 다시 생성해야할 필요가 없다. 그래서 이때 사용하는 것이 useCallback이다.

const log = () => console.log('로그를 찍어보는 함수입니다');

위와 같은 단순히 로그를 찍어보기 위한 함수가 있다고 가정했을때, 이 log라는 함수는 상태가 어떻게 변하더라도 항상 로그를 찍는건 동일하다. 상태의 변함과 상관이 없다면 매번 새로 생성할 필요가 없으며 캐싱하여 사용하여도 된다.

const log = useCallback(() => {
  console.log('로그를 찍어보는 함수입니다');
}, []);

useCallback의 두번째 인자로 받는 배열은 해당 함수가 재생성 되어야만 하는 상태가 있는 경우, 해당 상태를 넣어주면 된다. 만약 log 함수가 title이라는 상태가 변경될 때마다 새로 생성되어야만 한다면 아래와 같다.

const [title, setTitle] = useState('제목');
const log = useCallback(() => {
  console.log(`로그를 찍어보는 함수입니다. 현재 타이틀은: ${title}입니다`);
}, [title]);

메모이제이션 - useMemo

useCallback이 함수라면 useMemo는 값의 재생성을 막기 위해 사용한다. width라는 상태가 변하면 area를 다시 계산하고자 하며 다른 상태의 변화에는 영향을 받지 않는다.

const HEIGHT = 100;
const [width, setWidth] = useState(100);
const area = useMemo(() => {
  return width * HEIGHT;
}, [width]);

이렇게 작성하고나니 useMemouseCallback의 차이가 없어보인다. 공식 문서에서도 useCallback를 useMemo로 표현할 수 있다고 하는데 useMemo로 전달된 함수는 렌더링 중에 실행된다라는 설명이 되어있다. useCallback은 상태가 변화하고 렌더링 되기 전에 함수가 호출되며 useMemo는 값을 반환하기 때문에 렌더링 도중에 가져오는 것은 아닐까?

이 useCallback은 필요한 것인가요?

메모이제이션을 알고 난 후 컴포넌트 내에 모두 useMemo, useCallback을 사용하였다. 그리고 커밋을 하려던 순간 이게 과연 효율성 있는 일인가?하는 의문점이 들었다. 메모이제이션을 하기 위해 결국 이것을 메모리에 저장하고 있어야 하는데 이 화면이 다시 렌더링 되는 몇번의 재사용성을 위해 메모리에 들고 있는 게 효율적인것일까? 클라이언트의 성능은 다시 그리는게 메모리에 저장하는게 필요하다고 느껴지지 않을 만큼 속도도 빠르다. 이런 고민을 하며 사용했던 useCallback, useMemo를 커밋하지 않고 얼마 후 팀 내 스터디 자리에서 사용된 예제에 적혀있던 코드를 보고 이 useCallback은 되고 있는 것인가요? 필요할까요? 질문하였다.

리액트의 목적은 컴포넌트를 재활용하여 화면을 그리는 것이며 이에 컴포넌트가 재활용 될 수 있도록 memoization을 하는것이 의도인 것 같다.

정말 뼈때리는 기분이 들었고 내가 했던 생각들을 되돌아보니 속이 다 시원했다. 지금 당장은 메모이제이션이 불필요해보이지만 리액트는 컴포넌트들을 재활용하여 만들어지는 뷰 라이브러리이다. 그렇다면 지금 작성된 컴포넌트가 어디서 재활용될지 아직은 모른다. 언제 재활용될지 모르지만 이 컴포넌트가 의존하고 있는 상태과 상관없이 계속 재렌더링 된다면 이 또한 얼마나 잠재적인 이슈일까 생각해보았다. 컴포넌트가 언제 어디서 재활용 될 지 모르고, 부모 컴포넌트의 상태가 변경되서 다시 렌더링 호출 될 지 모르기 때문에 memoization을 예방하는 차원에서 사용한다.