사람들은 왜 리액트 상태 관리를 위해서 외부 라이브러리를 사용할까

서건혁·2024년 2월 15일
1
post-thumbnail

개요

리액트에서는 상위 컴포넌트의 상태를 하위 컴포넌트에게 효율적이게 전달하기 위해서 다양한 라이브러리를 제공하고 있다. 나도 라이브러리를 많이 들었지만, 처음에 들었던 생각은 ‘상태관리를 편하게 하기 위한 다양한 라이브러리가 있구나’ 정도였다. 왜 props와 context API를 사용하지 않고 Redux, MobX, Recoil, Zustand, React Query 같은 라이브러리를 사용하는 이유에 대해서는 제대로 알고 있지 않았다. 근데 이번에 개발하게 되면서 어쩌다 보니 상태 관리에 대해서 고민해야 하는 경우가 생겼다. 내가 이 글을 작성하는 이유는 ‘다른 사람들도 나와 같은 고민을 해 본 적이 있지 않을까?’ 라는 의문과 함께 그 고민을 아직도 가지고 있는 사람들을 위해서 같이 정보 공유를 하고자 쓴 글이다.

Props Drilling

상태 관리 라이브러리를 쓰는 핵심 이유는 바로 이것 때문이다. Props drilling이란 최상위 컴포넌트가 하위 컴포넌트에게 props를 전달할 때 그 사이에 있는 상위 컴포넌트가 하위 컴포넌트에게 정보를 주입하는 역할로만 쓰이는 것을 말한다. 예를 들어 다음과 같은 경우가 있다고 해보자.

위의 그림을 봤을 때, 하위 컴포넌트에 구성은 최상위 컴포넌트에 의해서 결정된다.

최상위 컴포넌트가 가지고 있는 정보에 따라 하위 컴포넌트가 재렌더링 될 것이기 때문이다. (그 사이에 있는 상위 컴포넌트도 재렌더링 되겠지만 지금은 별로 중요하지 않다.)

그렇다면 이 때, 상위 컴포넌트의 역할은 무엇일까?

바로 최상위 컴포넌트에게 props를 활용해서 정보를 주입받고 그 주입받은 정보를 하위 컴포넌트에게 넘겨주는 것이다.

이런 식으로 구현을 완료했다. 어떤 사람은 여기서 만족할 수도 있을 것이다. 하지만 우리는 여기서 더 나아가 최적화를 하고자 한다.

구현 자체의 문제는 없지만, 굳이 찾아보자면 뭐가 있을까?

여기서 상위 컴포넌트는 받은 정보를 주입받은 일만 할 뿐 그외에는 아무런 일도 하지 않았다.

왜냐하면 상위 컴포넌트는 최상위 컴포넌트의 상태에 종속되는 컴포넌트가 아니기 때문이다.

좀 더 예시를 들어보자.

여기서 하위 Text 컴포넌트는 최상위 Text 컴포넌트에 작성된 텍스트에 맞춰 똑같은 텍스트를 가진다고 한다.

최상위 컴포넌트가 ‘wow’라는 텍스트를 가지고 있다면 하위 컴포넌트도 ‘wow’라는 텍스트를 가질 것이다. 그럼 이때 div 컴포넌트는 뭘 가질 수 있을까?

div 컴포넌트는 가질 수 있는 게 없다. 왜냐하면 Text 컴포넌트가 아니라 글자를 가질 수 없기 때문이다. 이제 ‘상위 컴포넌트는 받은 정보를 주입받은 일만 할 뿐 그외에는 아무런 일도 하지 않았다.’라는 말이 무슨 뜻인지 감이 왔을 것이다.

우리는 이러한 현상을 보수적으로 봐야한다. 해당 정보가 필요없는 상위 컴포넌트가 자신이 관리하고 있는 하위 컴포넌트를 위해서 정보를 관리한다는 사실을 안 좋게 생각해야 하는 것이다.

그 이유는

  • 상위 컴포넌트가 하위 컴포넌트에게 정보를 넘겨 주기 전에 실수로 정보를 재가공 해버려서 최상위 컴포넌트의 정보가 변형돼버릴 수 있기 때문이다.
  • 만약 상위 컴포넌트가 최상위 컴포넌트에게 받는 props가 엄청나게 많다면? 그리고 상위 컴포넌트 하위에 또 하위 컴포넌트가 여러 개 있고 그 하위 컴포넌트에 또 하위 컴포넌트가 있고…. 이런식으로 props와 컴포넌트 상하 구조가 엄청나게 복잡해질 경우에는 개발자 입장에서 정보를 주입하는 과정이 힘들어지기 때문이다.

그래서 사람들은 이러한 문제들을 해결하기 위해서 노력했다. 그것이 바로 Context API다.

Context API

Context API는 props와 다르게 다음과 같은 구조를 지닌다.

Context API는 props와 다르게 상위 컴포넌트에게 정보를 전달하고 하위 컴포넌트로 그 정보를 다시 전달하는 과정을 거치지 않아도 된다. 왜냐하면 Context API로 선언된 값은 해당 Context API와 연결되어 있는 Provider 소속에 한해서 전역적으로 그 값을 불러올 수 있기 때문이다. 그래서 상위 컴포넌트가 하위 컴포넌트로 props로 정보를 보내지 않아도 하위 컴포넌트는 Provider 그룹에 속하기 때문에 최상위 컴포넌트가 Context API로 등록한 값(정보)를 바로 불러올 수 있다.

여기서 값이라고 표시한 이유는 Context API는 State 뿐만 아니라 함수같은 다양한 값들도 Context API를 통해 등록할 수 있기 때문이다.

좋아 이대로 끝났다… 라고 말하고 싶지만 아직도 문제가 있다.

그것은 바로 Provider 그룹의 등록 문제다.

Provider 그룹에 하위 컴포넌트를 소속하게 하기 위해서는 다음과 같은 과정을 거쳐야 한다.

Provider 그룹에 하위 컴포넌트를 집어 넣기 위해서는 불필요한 상위 컴포넌트도 Provider 그룹에 등록시켜야 한다.

아까도 말했지만 Provider 그룹에 소속된 컴포넌트는 Context API로 선언된 값에 접근할 수 있다고 했다. 강조하지만 이러한 구조는 좋지 않다.(그 이유에 대해서는 아까 위에 언급했다.)

그리고 또 하나 더 문제가 있다. 이것은 Context API의 특성에 의해서 비롯되는 문제다.

Context API로 등록된 값이 만약 변경될 경우 Provider 그룹에 속한 컴포넌트도 재렌더링이 되어야 한다. 이것은 당연하다. 등록된 값이 바뀌었으니, 그 값을 반영하기 위해서 재렌더링을 해야 할 것 아닌가?

하지만 문제는 Provider 그룹에 상위 컴포넌트가 있다는 사실이다.

개발자 입장에서는 해당 값을 사용하고 있는 컴포넌트만 바뀌기를 원하는데, 의도치 않은 상위 컴포넌트도 재렌더링이 되어버리니 성능 문제에 대해서 고민이 많아진다.

물론 이것들을 해결할 수 있는 방법이 있다.(useMemo를 사용하는 등의 방법이 그 해결책이라고 할 수 있겠다.)

하지만 이러한 방법을 도입하여 Context API를 사용하는 것은 배보다 배꼽이 더 큰 경우가 될 수 있을 것이다. (Provider 그룹에 속하는 모든 컴포넌트마다 useMemo를 사용할 생각을 하면 끔찍하다.)

그래서 Context API를 사용할 때는 다음과 같은 상황을 고려하는 게 좋다고 생각한다.

  • 해당 값이 상위 컴포넌트로 값이 props를 통해 넘어가는 것을 원치 않고 하위 컴포넌트에서 바로 받을 수 있게 해야 하는 상황.
  • 값이 잘 변경되지 않는 상황(예를 들어 언어를 선택하면 해당 언어에 맞게 사이트 정보가 제공되는 경우)

결론

근데 솔직히 값이 잘 변경되지 않는 상황이 많을까?

나는 그렇게 생각하지 않는다.

우리가 React 라이브러리로 웹 개발을 하는 이유는 사실 SPA 구현을 위해서지만 이러한 상태 관리를 통해 반응형 웹 사이트를 쉽게 만들 수 있기 때문이다.

그렇기 때문에 저런 상황에 처할 일은 대부분 없을 것이다. 그러므로 우리는 이러한 고민을 해결해야 한다.

  • 상태의 값이 자주 바뀌는 경우에도 적용할 수 있어야 한다.
  • 해당 정보가 필요한 컴포넌트만 재렌더링 되어야 한다.
  • props drilling을 통한 정보 주입을 받으면 안된다.

그리고 이러한 고민들을 극복해 주기 위해 다양한 상태 관리 라이브러리가 나온 것이다.

지금까지 왜 상태 관리를 위해서 외부 라이브러리를 사용하는 이유에 대해서 알아보았다.

상태 관리는 React 라이브러리로 개발하는 사람이라면 꼭 알아야 할 중요한 핵심이라고 생각한다. 이러한 상태 관리는 성능에도 크나큰 영향을 미칠 수 있으니, 해당 부분에 대해서 좀 더 파고 들어 공부하면 좋겠다는 게 나의 생각이다.

만약 해당 글에 잘못된 정보가 있으면 바로 알려주었으면 좋겠습니다.

참고 자료

22. Context API 를 사용한 전역 값 관리 · GitBook

How to Avoid Prop Drilling in React

Prop Drilling

React 에서 Context API 와 상태관리

0개의 댓글