대표적인 라이브러리로 3가지가 있다.
이러한 라이브러리들은 상태 관리를 용이하게 해주기 위해
다음과 같은 기능들을 제공한다.
Props Drilling 이슈란, A라는 컴포넌트에 상태가 있고
I라는 컴포넌트가 해당 상태를 사용한다고 하면
중간에 존재하는 C, G 컴포넌트에서는 굳이 name이라는 상태가 필요하지 않음에도,
컴포넌트에 props를 만들어 자식 컴포넌트에 넘겨주어야 한다.
이를 props drilling(프로퍼티 내려꽂기)문제라고 부른다.
만약 전역 상태 저장소가 있고 어디서든 접근 할 수 있다면 이런 문제를 해결할 수 있을 것이다.
이런 상태 관리 툴이 반드시 필요한것은 아니다.
Redux의 개발자인 Dan Abamov도 "You Might Not Need Redux"라는 아티클을 통해
React 공식 문서의 "React로 생각하기"만 잘 따라와도 대부분의 문제를 해결할 수 있다고 언급한다.
MVC라고 하는 디자인 패턴은 Controller에서는 Model의 데이터를 조회하거나 업데이트 하는 역활을 한다.
이렇게 Model이 업데이트 되면, View는 화면에 렌더링된다.
반대로 View가 Model을 업데이트 할 수도 있다. Model이 업데이트 되어 View가 업데이트 되고
업데이트된 View가 다시 다른 Model을 업데이트 한다면, 또 다른 View가 업데이트 될 수 있다.
복잡하지 않은 어플리케이션에서는 이런 양방향 데이터 흐름이 문제되지 않지만
어플리케이션이 복잡해질수록 양방향 데이터 흐름의 시스템 복잡도가 기하급수적으로 증가하고
예측 불가능한 코드를 만들게 된다.
Facebook은 이런 MVC 모델의 양방향 데이터 흐름이 만들어낸 버그중 하나를 알림 버그를 뽑았다.
페이스북에 로그인 했을 때 화면 위 메시지 아이콘에 알림이 떠있지만
클릭하면 아무런 메시지가 없는 버그를 본적이 있다.
해당 버그를 수정하고 몇일간은 괜찬았지만
업데이트를 하다보면 다시 버그가 나타나고 수정하는일이 반복되었다.
페이스북은 알림 버그의 원인으로 양방향 데이터 흐름으로 보고
단방향 데이터 흐름의 디자인 패턴인 Flux
를 도입하게 되었다.
리덕스는 이런 단방향 데이터흐름을 가지는 디자인 패턴 Flux에서 영감을 받아만들어 졌다.
Flux 디자인 패턴의 가장 큰 특징은 단방향 데이터 흐름이다.
데이터 흐름은 항상 Dispatcher에서 Store로, Store에서 View로,
View는 Action을 통해 다시 Dispatcher로 데이터가 흐른다.
Flux를 크게 Dispatcher, Storem View 세부분으로 구성된다.
디스패처는 플럭스의 모든 데이터 흐름을 관리하는 허브 역활을 담당한다.
액션이 발생되면 디스패처로 전달되는데, 디스패처는 전달된 액션을 보고
등록된 콜백함수를 실행하여 스토어에 데이터를 전달한다.
디스패처는 전체 어플리케이션에서 한개의 인스턴스만 사용된다.
어플리케이션의 모든 상태 변경은 Store에 의해 결정된다.
디스패처로부터 메시지를 수신받기 위해서는 디스패처에 콜백 함수를 등록해야한다.
Store가 변경되면 View에 변경되었다는 사실을 알려주게된다.
Store는 싱글톤으로 관리된다.
플럭스의 뷰는 화면에 나타내는것 뿐만아니라,
자식 뷰로 데이터를 흘려 보내는 뷰 컨트롤러 역활도 함께 한다.
디스패처에서 콜백함수가 실행되면 스토어가 업데이트 되게 되는데,
이 콜백 함수를 실행할 때 데이터가 담겨있는 객체가 인자로 전달되어야 한다.
이 전달되는 객체를 액션이라고 하는데,
이 액션은 대체로 액션 생성자(Action creator)에서 만들어진다.
액션이 디스패처로 데이터를 처리하는 동안 다른 액션이 들어오게 된다면,
새로 들어온 액션은 처리하고있던 액션의 작업이 끝날때까지 대기하게 된다.
리덕스를 사용하면 state(상태)값을 컴포넌트에 종속시키지 않고
상태 관리를 컴포넌트 바깥에서 관리할 수 있게된다.
리덕스를 이용한 상태 관리 과정은 다음과 같다.
리덕스 프로젝트를 적용하게되면 스토어라는 것이 생기게 된다.
스토어 안에는 프로젝트의 상태에 관한 데이터들이 담겨있다.
컴포넌트는 스토어에 구독을 한다.
구독하는 과정에서 특정 함수(subscribe(listener)
)가 스토어에 전달된다.
그리고 나중에 스토어의 상태값에 변동이 생기면 전달받은 함수를 호출하게 된다.
이제 컴포넌트에 어떤 이벤트가 생겨서, 상태를 변화할 일이 생겼다면
이 때 dispatch(action)
라는 함수를 통해 액션을 스토어에 던져준다.
액션은 상태에 변화를 일으킬 때 참조할 수 있는 객체이다.
이 액션 객체에는 필수적으로 type이라는 값을 가지고 있어야 한다.
예를들어 {type: 'INCREMENT'}
이런 객체를 전달받게 된다면
리덕스 스토어에서는 이 액션을 참조해 값을 더하게 된다.
추가적으로 상태값에 2를 더해야 한다면,
{type: 'INCREMENT', diff: 2}
이런 액션 객체를 만들게 된다.
그러면 나중에 이 diff
값을 참조해서 기존 값에 2를 더하게 된다.
type
을 제외한 값은 선택적인(optional) 값이다.
액션 객체를 받으면 전달받은 액션의 타입에 어떻게 상태를 업데이트 할지 정의해줘야 한다.
이러한 업데이트 로직을 정의하는 함수를 Reducer(리듀서)라고 한다.
이 함수는 우리가 직접 구현해야 한다.
예를들어 type
이 INCREMENT라는 액션이 들어오면 숫자를 더하고,
DECREMENT라는 액션이 들어오면 숫자를 감소시키는 작업을 여기서 하면 된다.
리듀서 함수는 두가지 파라미터를 받는다.
이 두가지 파라미터를 참조하여 새로운 상태 객체를 만들어서 반환한다.
상태에 변화가 생기면 이전에 컴포넌트가 스토어에 구독할 때 전달했던 함수 listener
가 호출된다.
이를 통해 컴포넌트는 새로운 상태를 받게되고, 이에 따라 컴포넌트는 리렌더리 하게된다.
기존에는 부모에서 자식까지 상태가 흘러가며 상태를 변경했었는데
리덕스를 이용하면 스토어라는 저장공간을 사용해 상태를 컴포넌트 바깥에두고
스토어를 거쳐서 상태를 업데이트 하거나 새로운 상태를 전달 받는다.
이런식으로 여러컴포넌트를 거치지 않기 때문에 깊숙한 컴포넌트 에서도
직속 부모에게 상태를 받는것 처럼 원하는 상태를 골라서 props를 편리하게 받아올 수 있다.
리덕스는 상태를 저장하기 위해 한개의 스토어만 사용한다.
(Flux 디자인 패턴과이 차이점이다. Flux는 여러개의 스토어를 사용한다.)
상태를 변경할 수 있는 유일한 방법은 액션을 넘겨주는 방법이다.
어플리케이션은 직접 상태를 변경할 수 없고,
상태를 변경하기 위해서는 액션을 디스패치로 넘겨야 한다.
액션을 어떤 변화가 있는지 정의하는 객체라고 한다면,
리듀서는 액션을 가지고 상태를 변경시켜준다.
순수함수가 지켜야 할 것
- 외부 네트워크 혹은 데이터 베이스에 접근하지 않아야 한다.
- 반환값은 오직 파라미터 값에만 의존해야 한다.
- 인수는 변경되지 않아야 한다.
- 같은 인수로 실행된 함수는 항상 같은 결과를 반환해야 한다.
- 순수하지 않은 API 호출은 하지 말아야 한다(Date 및 Math)