etc
React의 전역 상태 관리
React에서 전역 상태 관리는 왜 필요할까?
리액트는 단방향 데이터 바인딩이라는 특징을 가진다. 단방향 데이터 바인딩 특징은 어플리케이션이 예측 가능하다는 장점이 있지만, 뷰를 업데이트 해주는 부분을 매번 작성해주어야 한다는 단점이 있다. 리액트의 경우 뷰가 state에 따라서 자동으로 갱신되어 뷰를 업데이트 해주는 코드를 작성할 필요는 없지만, 이 state를 어플리케이션 구조에 맞게 잘 전달해주는 과정이 필요하다. 만일 컴포넌트 구조가 매우 깊은 트리 구조라면 해당 state를 넘겨주는데 여러 단계를 거쳐야하고, 이 state가 어디서부터 전달되었는지 추적하기 어려울 것이다.
이러한 문제를 해결하기위해 전역 상태 관리 도구는 어떠한 컴포넌트에서도 전역으로 관리되는 state에 바로 접근할 수 있도록 해준다. state를 전달하기위해 여러 단계를 거칠 필요가 없는 것이다.
해당 상태가 어플리케이션 전반에 이용되어 상태를 전달하기 위한 단계가 복잡할 경우, 혹은 전반에 이용되지 않더라도 상태를 전달하기 위한 단계가 복잡할 경우 사용된다.
전역 상태 관리를 위한 도구들
Redux
리덕스는 리액트 이외에도 자바스크립트로 만들어진 어플리케이션이라면 사용할 수 있다.
리덕스는 액션이라고 불리는 이벤트를 통해서만 state를 업데이트 시킬 수 있다. 이는 리액트가 사용하는 단방향 데이터 바인딩과 같은 원리로 상태 변경을 예측하기 쉽도록 한다. 언제, 어디서, 왜, 어떻게 state가 변경되었는지 파악할 수 있다.
리덕스의 이러한 구조는 상태의 변경이 예측 가능하다는 장점도 있지만, 러닝 커브가 있고 구현하기위해 더 많은 코드를 작성해야한다.
언제 리덕스를 도입하면 좋을까
- 어플리케이션이 많은 상태를 보유하고, 어플리케이션의 여러 공간에서 사용될 때
- 상태가 매우 자주 변경될 때
- 상태 변경 로직이 복잡할 때
- 어플리케이션의 규모가 클 때, 많은 사람들과 작업해야 할 때
예측 가능하고, 테스트가 쉽다는 장점에 대규모 어플리케이션에 적합하다. 또한 상태 변경을 손쉽게 추적할 수 있기에 상태 변경 로직이 복잡할 때도 사용하면 좋다.
주요 개념
- 스토어는 전역 state를 저장하는 곳으로 자바스크립트 object인데 특별한 함수와 기능을 내장하고 있어 순수 전역 객체와 다르다.
- 우리는 스토어 내부의 state를 직접적으로 수정하면 안된다. 이는 정해진 방식으로만 state를 조작하여 예측가능하다는 redux의 원칙을 위배한다.
- state를 변경하는 방법은 순수 액션 객체를 만들고 활용함으로써 가능하다. 액션 객체는 어플리케이션에 무슨 일이 발생할 것인지 알려준다. 그리고 dispatch를 사용해서, action을 전달한다.
- 액션이 dispatch되면, reducer를 실행시키고 old state를 기반으로 new state를 계산하여 업데이트한다. old state에서 new state로 변경시킬 때 immutable을 유지해야한다. 만약 immutable하지 않다면 old state와 new state를 비교할 때 문제가 발생하고 리듀서가 올바르게 작동하지 않는다. (객체는 참조값이기 때문에.)
- 마지막으로 스토어는 subscribers에게 구독중인 state가 변경되었다고 알려주고 UI가 변경되게 된다.
Recoil
Recoil은 facebook에서 출시한 상태관리 라이브러리이다.
state는 공통된 상위 컴포넌트로 끌어올려야만 공유될 수 있으며 이 과정에서 거대한 트리가 다시 렌더링 되는 문제와 Context는 단일값만 저장할 수 있으며, 자체 소비자를 가지는 여러 값들의 집합을 담을 수 없다는 문제를 해결하기위해 탄생했다.
Recoil의 특징
boiler-plate free api로 redux는 간단한 상태 한개만 처리하려고 해도 수많은 보일러 플레이트 코드가 필요하였으나 recoil은 매우 간단하게 사용할 수 있다. 즉 러닝커브가 낮다고 할 수 있다. 또한 비동기처리를 하기 위해서 redux의 경우는 redux-thunk와 같은 비동기 처리 라이브러리에 의존했지만 recoil에는 내장되어 있다.
주요 개념
- atoms
- atoms는 상태의 단위이며 업데이트와 구독이 가능하다. atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 받아 리렌더링 된다. atoms는 런타임 중에서도 생성될 수 있다.
- selectors
- atoms나 다른 selectors를 입력으로 받아들이는 순수 함수다. 상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링된다. selectors는 상태를 기반으로 파생된 데이터를 사용하는데 이용된다. 최소한의 상태 집합만 atoms에 저장하고 파생된 데이터는 selectos를 통해서 처리함으로써 불필요한 상태의 보존을 방지할 수 있다.
Context api
Context api는 react에 자체적으로 내장된 전역 상태 관리 도구이다.
언제 Context api를 사용하면 좋을까?
상태 변경이 빈번하게 일어나지 않을 때, 간단한 데이터를 전역으로 공유하고 싶을 때 사용하면 좋다. Provider의 모든 하위 컴포넌트가 렌더링이 일어나기 때문이다. 예시로 언어 설정이나, 다크 모드 변경과 같은 스타일 설정을 저장해놓는데 적합하다.
서버 데이터 관리
위투디 정식 서비스를 제작할 때 서버 데이터 관리를 위해서 redux를 활용했다. 하지만 Redux는 비동기 처리가 불가능하기에 redux-thunk와 같은 비동기 미들웨어를 사용해야 했고, 보일러 플레이트 코드가 너무 많았다.
또한 서버 데이터의 만료 시간 등, 유효성 처리를 직접 해주어야 해서 번거로웠다.
이후에는 react-query라는 라이브러리를 사용했다. redux처럼 많은 보일러 플레이트 코드들이 필요없었고, 서버 데이터 관리를 위한 다양한 기능을 제공하여 개발 생산성이 매우 높아졌다. 또한 최근에는 Suspense를 지원하기에 데이터를 fetch 할 동안 fallback-ui를 보여줄 수 있다. 매번 컴포넌트마다 loading 관련 분기를 만들어 처리하지 않아도 된다. 마치 try-catch 구문처럼 관심사를 분리하여 더 효율적이고 가독성 높은 코드를 만들 수 있는 것이다.
서버 데이터를 관리함에 있어서, 데이터를 다루는 매우 복잡한 로직이 있지 않는 이상 react-query를 사용하는 편이 개발 생산성에 도움을 준다고 생각한다.