리액트에서의 Single Source of Truth, 근데 React-Query를 곁들인

Eric Park | 박준열·2023년 2월 8일
26
post-thumbnail

리액트에서 가장 중요한 요소가 무엇이냐고 물어본다면 많은 사람들이 상태라고 대답할 것이다.

리액트에서 상태(State)란 특정 컴포넌트의 기억이라고 표현할 수 있다.

어떤 컴포넌트가 어떤 글을 보여줘야하는지, 배경화면이 어떤 색이어야하는지 등등의 정보들은 우리는 컴포넌트의 상태에 저장을 한다. 물론 변경되지 않을 값이라면 따로 상태로 저장을 할 필요는 없다.

  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

출처: https://beta.reactjs.org/learn/state-a-components-memory

이런식으로 리액트에서는 컴포넌트에 필요한 값들과 정보를 상태라는 개념으로 다룬다.

공통적인 상태

예를 들어 두개의 컴포넌트가 공통된 정보가 필요하다고 가정을 해보자.

대충 이런 경우일 것이다

컴포넌트 1에서도 상품 리스트 정보가 필요하고, 컴포넌트 2에서도 상품 리스트 정보가 필요한 상황이다.

이런 상황에서 가장 처음 떠올릴 수 있는 방식은 각각의 컴포넌트에서 각자의 상태로 상품 리스트 정보를 관리하는 방법이다.

되게 직관적이기도 하고, 가장 쉬운 방식이기도 하다. 정보의 변경이 필요하면 컴포넌트 1에서 업데이트해주고, 컴포넌트 2에서도 업데이트 해주면 되니까.

말도 안되는 소리

리액트를 처음 배우고 시작한지 얼마 안된 시절의 나는 이런 방식으로 개발을 했었다. 뭐가 문젠지도 몰랐고, 상태에 대해서 깊게 고민을 해본적이 없었기 때문이다.

그러다가 어느 상황에서 갑자기 두개의 컴포넌트가 각자 다른 정보를 보여주는 오류가 발생했었다. 처음에는 왜 이런 오류가 발생하는지를 몰라서 디버깅하는데 아주 고통을 받았었다.

몇시간동안 계속 "컴포넌트가 동기화가 안돼요" 라고 구글링을 하고 있었고, 당연히 제대로 된 답변을 찾을 수가 없었다. 아마 너무 초보적인 실수라서 그런 것도 있을 것이고, 내가 내 문제에 대해서 제대로 알지 못했기 때문에 구글링을 제대로 못한 탓도 있을 것이다.

그러다가 컴포넌트 2에서 실수로 상태 변경하는 코드 딱 한줄을 까먹고 안적었다는 사실을 발견했고, 그제서야 뭔가가 잘못됐다는 것을 슬슬 체감하기 시작했다.

아니 만약 컴포넌트가 두개가 아니라 열개면... 이걸 열번 반복해야한다고..?

뭔가가 이상하다는 것을 깨달은 나는 제대로 상태에 대해서 공부를 해봐야겠다고 다짐을 했었고, 이것저것 공부한 덕분에 State Lifting이라는 것에 대해서 알 수 있었다

State Lifting: 공통된 하나의 부모 컴포넌트에 상태를 보관하고, Props로 자식에게 상태를 전달하는 방식

State Lifting을 알게 됐다

공부를 한 덕분에 나는 아까와 같은 바보같은 실수를 더 이상 하지 않게 되었고, 그 이후부턴 같은 정보를 보여줘야하는 컴포넌트가 각자 다른 정보를 보여주는 그런 오류는 겪지 않을 수 있었다.

백엔드에서 받아온 정보는 어떻게 관리하지 ?

일반적으로 사람들은 프론트엔드 개발을 할 때, 백엔드에서 정보를 받아와야 하는 상황에서는

  1. 컴포넌트에서 useState를 통해 state를 만들고
  2. useEffect를 통해서 GET 요청을 날리고
  3. 받아온 데이터를 setState를 통해 state에 저장을 한다

그러면 이제 View단에서는 그 state를 기반으로 막 정보를 보여주는 것이다.

솔직히 말하면 불과 몇개월전까지도 이게 조금이라도 잘못된 것이라고는 생각조차 못했다

그러다가 React-Query를 한번 사용해보고 싶어서 토이프로젝트에 적용을 하고 있던 상황이었다.

React-Query는 비동기 처리를 더욱 간편하게 할 수 있도록 도와주는 라이브러리로, Client-State와 Server-State를 분리하자 라는 목적을 갖고 있다.

Client-State vs Server-State

우선 Client State와 Server State의 차이를 알아보자

Client State란 백엔드에 전달될 필요가 없는 클라이언트의 상태

예를 들어서, 현재 앱에

  1. 모달창이 열려있는지 닫혀있는지
  2. 다크모드가 켜져있는지 꺼져있는지
  3. 장바구니에 담겨있는 상품들의 종류

등등을 관리하는 상태가 있다고 가정을 하면, 이 정보들은 굳이 백엔드에 전달을 해서 DB에 저장을 할 필요가 없는 정보들이다.

Server State란 백엔드에서 주로 관리하고, 요청에 의해 프론트엔드로 전달되는 데이터

예를 들어서,

  1. 이 쇼핑몰에서 판매하는 상품들의 리스트
  2. 어떤 유저가 찜하기를 해놓은 상품들의 리스트

등등의 정보들은 기기가 달라지더라도 유저만 같다면 정보가 동일해야하는 데이터들이다. 그렇기에 프론트엔드에서는 이런 정보들을 Client State로 관리하지 않고, 매번 백엔드 GET/POST요청을 통해 관리한다.

그럼 아까 언급했던 "백엔드에서 가져온 정보는 어떻게 관리하지 ?"에 대해 조금 더 얘기를 해보려 한다.

Single Source of Truth


출처: https://beta.reactjs.org/learn/sharing-state-between-components

영어라서 조금 간단하게 설명을 해보자면, 모든 상태들은 자신을 "소유"하는 특정 컴포넌트가 있어야 한다 라는 의미이다.

그럼 여기서 이제 아까 백엔드에서 받아온 데이터를 관리하는 방식이 조금 문제가 있을 수도 있다는 것을 깨달을 수가 있다.

  1. 컴포넌트에서 useState를 통해 state를 만들고
  2. useEffect를 통해서 GET 요청을 날리고
  3. 받아온 데이터를 setState를 통해 state에 저장을 한다

앞서 언급했던 이 과정은 백엔드에서 받아온 데이터를 Client-State에 복사를 하는 것이고, View단에서 보여지는 데이터는 이 복사된 데이터라는 것이다.

사실 이 부분은 문제가 있을수도, 없을수도 있다.

Redux나 Recoil같은 전역 상태관리 툴을 이용해서 백엔드에서 가져온 데이터를 이 전역 상태에 보관을 한다면, 다른 컴포넌트에서 이 데이터가 필요할 시에 전역 상태에서 데이터를 받아와서 사용하면 데이터의 중복이 발생하지 않는다고 봐도 된다.

다른 의미로, Single Source of Truth가 지켜진다는 의미이다.

하지만 여기서도 조금 문제가 되는 부분들이 존재한다.

  1. 애초에 Redux/Recoil은 Client 전역상태관리 툴이기에, 비동기 처리가 되게 복잡하고 까다롭다. 그리고 저장된 데이터가 최신 데이터라는 것을 보장하는 것이 쉽지 않다.

  2. 정말 많은 사람들은 두개의 멀리 떨어진 컴포넌트에서 같은 정보가 필요한 경우에, 전역상태에서 데이터를 받아오는 것이 아닌, 그냥 각자 백엔드에서 GET요청으로 데이터를 받아온다.

1번 같은 경우에는 Single Source of Truth 측면에서 문제가 발생하는 것은 아니다. 다만 DX(개발자 경험) 측면에서 안좋기도 하고, 간단한 기능인데도 보일러 플레이트 코드가 너무 많이 필요한 경우들이 있다. 배보다 배꼽이 더 큰 상황

2번은 이제 조금 문제가 된다. 왜 문제가 될까?

Single Source of Truth가 지켜지는 상황에 대해 다시 한번 생각을 해보자

모든 상태들은 자신을 "소유"하는 특정 컴포넌트가 있어야 한다

2번 같은 경우에서 그럼 상태를 "소유"하는 컴포넌트는 누구일까?

정답은 둘 다 이다.

그렇기에 문제가 발생하는 것이다.

하나의 상태를 하나 이상의 컴포넌트가 소유한다는 것은 다른 의미로 Single Source of Truth가 지켜지지 않는다는 의미인 것이다.

와 그렇구나! 근데 이게 React-Query랑 무슨 상관이야 ?

React-Query의 목적이 무엇인지 다시 한번 생각해보자.

Client-State와 Server-State의 분리

쉽게 생각하면 React-Query는 Client-State에 대해서 신경을 쓰지 않는다. React-Query는 오로지 Server-State에만 관심이 있다.

"React-Query": 너희가 Client-State를 어떻게 관리하는지는 전혀 신경쓰지 않을거야. 하지만 모든 Server-State는 내가 관리할거니까 절대로 의심하지 않아도 돼.

모든 백엔드 데이터는 React-Query를 거쳐가고, 클라이언트는 모든 데이터를 React-Query를 통해서 받아오게 된다는 의미인데, 모든 데이터를 React-Query를 통해 받아온다는 말, 어딘가 익숙하지 않은가?

모든 상태들은 자신을 "소유"하는 특정 컴포넌트가 있어야 한다

모든 정보를 소유하는 특정 컴포넌트가 React-Query라고 가정을 해보자. 그러면 "특정 컴포넌트"라는 말을 React-Query로 치환을 하게 되면

모든 Server-State들은 React-Query가 "소유"한다

Single Source of Truth가 지켜진다는 의미이다.

결론

아까도 얘기를 했었지만, 나는 Single Source of Truth가 지켜지지 않는 상황에 대해서 문제를 깨달은지 얼마 되지 않았다. 기껏해야 서너달?

프론트엔드 개발을 거의 1년반정도 했음에도 불구하고 나는 그동안 잘못된 방식으로 개발을 해오고 있었다는 것이다.

그러다가 React-Query를 사용해보고 싶어서 토이프로젝트에 도입하여 사용하던 도중, React-Query를 이용하니까 상태를 관리하는 것이 어딘가 모르게 훨씬 쉬워졌다는 사실을 깨닫게 되었다.

그래서 왜 쉬워진건지에 대해서 고민을 좀 길게 해보았다. 일주일정도 동안 계속 고민을 했었고, 중간중간 검색도 해보면서 내가 모르는 부분이 있나 계속 고민을 했다.

그러던 중 리액트 공식 문서에서 Single Source of Truth를 언급하는 부분을 읽는데 갑자기 머리속에서 전구가 갑자기 딱 켜지는 느낌을 받았고, 이 부분이 내가 고민하던 것에 대한 해답이라는 것을 깨닫게 되었다.

그동안 나는 Single Source of Truth에 대해서 알지 못했지만, 계속 개발을 하면서 나도 모르게 그 부분에 대해 위화감을 느끼고 있었나보다. 그러다 React-Query를 도입하고, Single Source of Truth가 지켜지게 되다보니 어딘지 모르게 불편하던 부분이 나도 모르는 사이에 해소가 되었던 것이다.

더 이상 React-Query 없이는 살 수 없는 몸이 되어버렸다

profile
꾸준한 개발자

1개의 댓글

comment-user-thumbnail
2023년 2월 15일

남들이 다 쓰니까 나도 써야지 라는 느낌보다 내가 어떤 문제에 봉착했고, 그 문제를 해결하기 위해 어떤 라이브러리를 썼다라는 글을 보면 정말 이상적인 개발자라는 느낌이 드네요!
덕분에 React-Query에 대해서 한 번 더 인지하고 갑니다.

답글 달기