변화하는 데이터를 말합니다. UI에 동적으로 표현되는 데이터, 즉, 사용자의 액션에 따라 변경될 수 있는 컴포넌트에 부분을 나타내는 자바스크립트 객체입니다.
변화하는 데이터를 관리하는 것을 말합니다. 변화하는 데이터를 알맞게 관리하기 위해 나온 개념입니다.
여러 컴포넌트 간의 데이터 전달과 이벤트 통신을 한 곳에서 관리하는 걸 의미합니다.
UI는 사용자와 끊임없이 상호작용합니다. 이전의 사용자들은 잦은 웹 페이지 이동이 있었는데, 페이지가 바귈때마다 서버에서 데이터를 가져오기 때문에 매번 페이지가 다시 그려졌습니다. 그런데 지금의 사용자는 이전의 페이지 같이 페이지 전환이 많은 웹 페이지를 사용하고싶어하지 않습니다. 데이터가 바뀌어도 페이지가 다시 렌더링 되지않고, 변하는 부분만 변하길 원합니다. 예를 들어 페이스북이나 인스타그램의 좋아요 버튼을 누른다고 해서 페이지가 새로고침 되지 않고, 실시간으로 좋아요가 반영되는 것을 말합니다.
그리고 지금은 웹 페이지의 상태들이 복잡합니다. 한 웹 페이지의 상태가 하나라면 굳이 관리할 필요가 없을 것이지만 상태가 많아지고 상태들이 서로 복잡하게 얽혀있다면 그 상태들이 상호간에 어떻게 의존하고 있는지, 상태들이 페이지 내부에서 어떻게 흘러가고 그에따라 UI가 어떻게 변하는지 알아차리기 어렵습니다. 게다가 이런 상태값들이 비동기적이라면 더욱 관리하기 힘들어 질것입니다. 하나의 페이지에서도 다양한 상태들이 존재하고 어떻게 변화하는지 제대로 알고 있어야하기 때문에 이를 효과적으로 관리할 필요가 있어졌습니다.
또한 리액트는 단방향 바인딩을 지원하기 때문에 부모에서 자식으로만 state
를 props
로 전달할 수 있고, 자식의 props
를 부모에게 직접 전달할 수 없기 때문에 자식에서 부모의 상태를 바꾸려면 해당 상태를 컨트롤하는 함수를 props
로 넘겨줘야합니다. 이것이 반복되면 props drilling
이 발생한다는 문제점이 있습니다. 프로젝트의 규모가 커질수록 props
의 깊이가 증가하게 되고, 이는 불필요한 리렌더링을 유발할 수도 있기때문에 상태관리가 필요합니다.
상태관리 라이브러리는 다양한 종류가 있습니다.
가장 대표적인 것은 Redux이고, 비슷한 기능을 하는 MobX, Recoil 등이 있습니다.
이 상태관리 라이브러리들은 대부분 전역 상태 관리를 지원합니다. useState
는 한 컴포넌트에서 만들어지고 그 하위의 상태를 넘겨받은 컴포넌트들만 가지게 되지만, 전역 상태 관리 라이브러리를 사용하면 꼭 그렇게 인접한 컴포넌트끼리 넘겨주는 방식을 거치지 않더라도 여기저기 컴포넌트에서 접근 할 수 있습니다. 일단 Props가 복잡해지는 문제는 해결됩니다.
이런 라이브러리의 경우 진짜로 값을 할당하는 부분은 한 곳에서만 일어난다는 특징이 있습니다.
공통적인 특징은 상태 업데이트부터 컴포넌트 리랜더링까지 데이터의 흐름을 한 방향으로, 한 지점을 거쳐가도록 설정한다는 것입니다.
리덕스가 나오기 전 리액트를 포함한 대부분의 프로젝트는 MVC 아키텍처가 많이 사용되었습니다. 컨트롤러가 여러 모델을 제어하고 모델과 뷰가 서로 바라보는 구조로, 모델과 뷰가 양방향으로 영향을 미치기 때문에 프로젝트 규모가 커지고 상태가 많아질수록 관리가 어렵습니다.
그래서 페이스북이 새로 내놓은 아키텍처가 Flux 아키텍처입니다.
Flux 아키텍처는 데이터의 흐름이 단방향으로 흐르는 구조입니다.
이러한 Flux 아키텍처를 가져가는 대표적인 라이브러리가 리덕스입니다.
store
(단일 스토어)만 가지며, 하나의 객체 트리를 가지기 때문에 디버깅에 용이합니다.store
에 모든 상태를 저장하는 중앙 집중 방식입니다.store
는 외부 요소이기 때문에 리액트의 내부 스케줄러에 접근할 수 없습니다.순수함수란?
부수효과가 없는 함수 즉, 어떤 함수에 동일한 인자를 주었을 때항상 같은 값을 리턴하는 함수 + 외부의 상태를 변경하지 않는 함수
MobX의 README는 MobX를 다음과 같이 정의하고 있습니다.
Anything that can be derived from the application state, should be derived. Automatically.
→ 어플리케이션의 상태에서 파생(derived)될 수 있는 모든 것은 자동으로 파생되어야 한다.
리덕스와 달리 불변성에는 신경쓰지 않아도 될 정도로 규칙만 잘 신경쓰면 최적화가 잘 됩니다.
리덕스보다 다루기 쉬운 라이브러리지만 분명한 단점들이 존재합니다.
그렇기에 장기적인 프로젝트, 유지보수 및 확장성을 고려해야 하는 프로젝트의 경우 MobX는 좋지않은 선택일 수 있습니다.
하지만 리덕스보다 러닝커브가 낮고 보일러플레이트 코드의 양 또한 적기 때문에 프로젝트의 규모가 크지 않다면 MobX를 사용하는 것은 좋은 해결책이 되리라 생각합니다.
러닝 커브란?
특정 기술 또는 지식을 실제 필요한 업무와 같은 환경에서 효율적으로 사용하기 위해 드는 학습 비용을 의미합니다.
보일러플레이트 코드란?
컴퓨터 프로그래밍에서 보일러플레이트 또는 보일러플레이트 코드라고 부르는 것은 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말합니다.
Facebook의 Dave McCabe가 개발한 React용 상태 관리 라이브러리입니다. 페이스북에서 비교적 최근에 나왔습니다.
리코일은 Atoms(공유 상태)
과 Selectors(순수 함수)
로 이루어져있습니다.
Atoms
은 상태의 단위이며, 업데이트와 구독이 가능합니다. 유니크한 키 값으로 구분됩니다. Atom
이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링됩니다.
Atoms
는 atom
함수를 사용해 생성합니다.
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
Atoms
는 디버깅, 지속성 및 모든 atoms
의 map
을 볼 수 있는 특정 고급 API에 사용되는 고유한 키가 필요합니다. 두 개의 atom
이 같은 키를 갖는 것은 오류이기 때문에 키 값은 전역적으로 고유하도록 해야합니다. React 컴포넌트의 상태처럼 기본값도 가집니다.
컴포넌트에서 atom을 읽고 쓰려면 useRecoilState
라는 훅을 사용합니다. useState
와 비슷하지만 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있습니다.
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => **setFontSize((size) => size + 1)**} style={{fontSize}}>
Click to Enlarge
</button>
);
}
버튼을 클릭하면 버튼의 글꼴 크기가 1만큼 증가하며, fontSizeState atom
을 사용하는 다른 컴포넌트의 글꼴 크기도 같이 변경됩니다.
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}
Selector
는 atoms
이나 다른 selectors
를 입력으로 받아들이는 순수함수(pure function)입니다.
또는 selectors
가 업데이트되면 하위의 selector
함수도 다시 실행됩니다. 컴포넌트들은 selectors
를 atoms
처럼 구독할 수 있으며 selectors
가 변경되면 컴포넌트들도 다시 렌더링됩니다.
Selectors
는 비동기처리 뿐만 아니라 데이터 캐싱 기능도 제공하기 때문에 비동기 데이터를 다루기에도 용이합니다.
Selectors
는 상태를 기반으로 하는 파생 데이터를 계산하는데 사용됩니다. 최소한의 상태 집합만 atoms
에 저장하고 다른 모든 파생되는 데이터는 selectors
에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지합니다.
Selectors
는 어떤 컴포넌트가 자신을 필요로하는지, 또 자신은 어떤 상태에 의존하는지를 추적하기 때문에 이러한 함수적인 접근 방식을 매우 효율적으로 만듭니다.
컴포넌트의 관점에서 보면 selectors
와 atoms
는 동일한 인터페이스를 가지므로 서로 대체할 수 있습니다.
Selectors
는 selector
함수를 사용해 정의합니다.
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
get 속성은 계산될 함수입니다. 전달되는 get 인자를 통해 atoms
와 다른 selectors
에 접근할 수 있습니다. 다른 atoms
나 selectors
에 접근하면 자동으로 종속 관계가 생성되므로, 참조했던 다른 atoms
나 selectors
가 업데이트되면 이 함수도 다시 실행됩니다.
fontSizeLabelState
예시에서 selector
는 fontSizeState
라는 하나의 atom
에 의존성을 갖습니다. 개념적으로 fontSizeLabelState selector
는 fontSizeState
를 입력으로 사용하고 형식화된 글꼴 크기 레이블을 출력으로 반환하는 순수 함수처럼 동작합니다.
스플레팅이란?
코드에서 당장 사용하는 부분만을 로딩하고, 현재 필요하지 않은 코드 부분은 따로 분리시켜 나중에 로드함으로써 로딩시간을 개선하는 것이 코드 스플리팅입니다.
그래서 어떤 상태관리 라이브러리를 쓰는게 좋나요?에 대한 대답은 각 라이브러리마다 장단점이 있고, 프로젝트마다 특성이 다르니 특성에 맞게 골라 쓰면 좋을 것 같습니다!
🔗 참고 링크
https://medium.com/hcleedev/web-상태-관리-라이브러리란-개념-redux-예시-acf48c51ae14
https://velog.io/@danmin20/상태관리-라이브러리-뭘-쓸까
https://www.youtube.com/watch?v=alsCMx6vpG4
https://velog.io/@mementomori/React-상태-관리-Tool-사용-비교-Redux-VS-MobX-VS-Context-API
https://doqtqu.tistory.com/336#toc-Redux 와 MobX의 차이점