Redux는 Flux와 같은 역할을 한다. (Flux는 앞서 내용 참조해주시길..!)
하지만 Flux 내에서도 문제가 있는데 이러한 문제를 해결하고자 더 상위버전으로 나온 것이 Redux라고 할 수 있다.
What is Redux?
1. Single Source of Truth
- Application의 Store를 위해, 단 한 개의 Store를 사용한다.
" Flux는 여러 Store를 사용하지만 Redux는 하나의 Store를 사용한다. "
- Store의 구조
- nested 구조로 component 별로 구조화 되어 있거나 event 별로 구조화 또는 App의 Data나 Component의 UI와 같은 틀로 구조화 되어 었음.
2. Store는 읽기 전용으로 되어 있기에 값을 직접 변경할 순 없다.
- Action을 통해서 Application의 Store를 변경하는 것..!! (그 자체가 dispatcher가 되는 것이다.)
3. Changes are made with pure functions
: action 객체를 처리하는 함수를 reducer라고 한다. 이 reducer는 정보를 받아서 상태를 어떻게 업데이트 할 지 정의한다.
reducer는 동기식 처리방법으로 구조화 되어 있으며 순수 함수로만 구현되어야 한다.
예:) 네트워크, DB 등 접근하는 내용이 있으면 안되며 인수 변경도 할 수 없다.
같은 인수로 실행된 함수는 언제나 같은 결과를 반환한다.
"순수하지 않은 API는 활용할 수 없다. " (ex : Date.now() , Math.random() )
그렇다면 Redux의 등장 배경이자 Flux의 문제가 무엇이 있는지 한 번 알아보고 Redux를 자세히 소개해보겠다.
Flux의 문제이자 Redux의 등장 배경
1. Flux에서는 스토어의 코드를 Application 상태를 삭제하지 않고서는 리로딩이 불가하다.
Store에는 상태변환의 로직과 현재 Application 상태를 가리키는 내용이 담겨져 있습니다.
스토어 객체 하나가 이 두가지를 가지고 있기 때문에 핫 리로딩 시에 문제가 발생하게 됩니다.
: 새로운 상태 변환 로직을 위해 스토어 객체를 리로딩하게 되면 기존의 상태까지 잃어버리고, 스토어와 시스템의 나머지 부분 사이에 있는 이벤트 구독까지 망가지게 된다.
이를 해결하고자 Redux에서의 Store는 스토어 객체를 분리시켰다.
스토어의 한 객체는 Application 상태를 가지고 있고 이 스토어 객체는 리로딩을 하지 않습니다.
다른 한 객체는 상태변환 로직을 갖게한다. 이 객체는 상태를 가지지 않기 때문에 상태가 변화한 뒤에 리로딩을 하도록 한다.
여기서 todos 라는 배열은 현재 Application 상태를 의미하고 이는 리로딩 시키지 않게 합니다.
addTodo() 라는 함수는 상태를 변화시키는 로직을 가지고 있는 함수로써 원래의 Application 상태 , 즉 todos라는 배열이
변화함에 따라 적용시켜야 하므로 addTodo 함수는 작업 이 후에 리로딩이 되도록 해야합니다.
2. Application 상태는 매 액션마다 재기록된다.
Time Travel Debugging : 다시 작업한 상태로 돌아간다.
이 전 Application 상태를 상태 객체의 버전들을 저장하는 배열에 추가한다 ( == git과 같은 느낌 )
하지만 javascript 작동방식 때문에 단순히 상태를 가진 변수를 배열에 추가하는 것으로는 부족하다.
Application 상태의 snapshot 생성이 아닌 같은 객체를 가리키는 새로운 포인터만 만들 뿐이기 때문.
액션에 대한 변화된 내용을 바로바로 state에 적용시키니 문제가 생기는 것입니다.
해결 방법으로 action을 다루는 reducer 중 서브 reducer가 state (현재 상태)를 액션 수 만큼 각각 복사하여 복사본을 하나 씩 가지고 있는다.
액션 이 후에 변화된 상태 값을 각 서브 reducer는 가지고 있고 이를 root reducer에게 전달하고 root reducer는 이를 통합하여
store에게 전달한다.
통합된 변화한 상태 값들은 스토어에서 현재 상태 값으로 대체된다 .
이렇게 복사본을 가지고 상태 변화를 적용시킨 뒤 통합해서 최종 상태 변경을 진행하게 되면 좋은 점이 Time Travel Debugging이 가능하다는 점이다. 각 복사본을 가지고 Time Travel 하면 되기 때문이다.
3. 서드파티 플러그인이 들어갈 좋은 장소가 없다.
개발자 도구가 여러 곳에서 쓸 수 있게 만드는 것이 좋은 구조인데, 그렇게 하기 위해선 기존 코드에 서드파티 플러그인을 추가할 수 있는 장소를 확장해야 하는 것이 중요하다.
Flux에서는 Dispatcher의 업데이트와 각 스토어의 업데이트를 구독해야만 했고, 이를 서드파티 모듈화 한다는 것은 힘든 작업이었다.
해결 방법으로는 시스템 부븐을 다른 객체로 감싸는 것이다.
이를 enhancer or higherorder 객체 혹은 미들웨어라고도 한다.
또한 상태 변환 로직을 트리를 사용해서 구조화하자.
다음과 같이 dispatch하는 것을 하나의 log (함수)로 묶는다. 이 부분이 Redux에선 Store인데 (Flux는 dispatcher)
action을 root reducer에게 전달해준다.
상태가 변화한 것을 알리기 위해 스토어가 단 하나의 이벤트만 보내어 알리면 된다. 이 이벤트는 모든 상태트리가
처리된 뒤에 보내지게 된다.
Redux의 요소
1. 액션 생성자
- Flux와 동일한 역할을 하며 Application 상태를 바꾸기 위해 액션을 만든다.
- 액션 생서자는 나머지 시스템들이 이해할 수 있게 포멧을 변경하여 메세지로 전달한다.
2. 스토어
- Flux에서는 Store가 상당히 많았고 dispatcher를 통해 action이 넘어오는 로직을 지켜야만 하였다.
또한 각 Store는 각자의 범위를 갖고 컨트롤러 역할을 하며, 컨트롤ㄹ러 뷰에게 상태 변경을 알려주어 뷰에 적용되도록 했다.
- Redux에서의 Store는 Dispatcher 역할 까지 겸해서 사용되며, Application 현 상태와 상태 변환 로직 두 가지로 나누어 각
객체에 저장하도록 한다.
- state tree를 총괄하는 역할을 하며 액션이 들어올 경우 어던 상태변화가 필요한 지 확인한 뒤, 일을 reducer 에게 위임하여 작업하도록 한다.
3. Reducer
- 스토어는 액션이 어떤 상태 변화를 만드는지 알 필요가 있을 때 Reducer에게 물어본다.
- root reducer는 전달 받은 state를 복사하여 sub reducer에게 전달한다.
- sub reducer는 변경 사항에 맞게 복사된 상태가 변경하도록 만들고 이를 root reducer에게 전달한다.
- root reducer는 이를 통합하여 하나의 상태 객체를 만들고 store에게 전달한다.
- reducer를 하나의 트리 구조로 묶어서 관리하기 때문에 단 한 번의 이벤트로 모든 작업을 진행할 수 있다.
- 수직적 구조이고 트리 레벨은 무제한으로 만들어 낼 수 있다.
4. View
- Flux에서는 컨트롤러 뷰와 이의 통제를 받는 일반 뷰가 있었다.
- Redux에서는 영민한 Component와 우둔한(우직한) Component가 있다. 각자의 역할은 서로 다르다
영민한 Component
- 1. 액션 처리를 담당
영민한 컴포넌트 밑의 우직한 컴포넌트가 액션을 보낼 필요가 있을 때 영민한 컴포넌트는 props를 통해서
우직한 컴포넌트에게 함수를 보낸다.
우직한 컴포넌트는 받은 함수를 콜백으로써 단순히 호출만 한다.
- 2. 자기 자신의 css style을 가지고 있지 않다.
- 3. 자기 자신의 DOM을 거의 가지고 있지 않다. 대신 DOM 요소들을 관리하는 우직한 컴포넌트들을 관리한다.
우직한 Component
- 액션에 직접 의존성이 없다. 이유는 영민한 Component로부터 props를 전달 받기 때문이다.
즉 , 우직한 컴포넌트들은 다른 로직을 갖는 다른 Application에서 재사용이 가능하다.
- CSS style도 포함하고 있다. style props를 받아 기본 style에 병합시켜서 style을 변경할 수도 있다.
5. View Layer Binding
- 뷰 트리를 위한 IT 부서
- 모든 컴포넌트들을 store에 연결시킨다.
- 많은 기술적인 세부사항을 처리해서 트리 구조가 세부 사항에 신경쓰지 않도록 만든다.
ㄱ. 공급 컴포넌트 (provider component)
- 컴포넌트 트리를 감싸는 컴포넌트
- connect()를 이용해서 루트 컴포넌트 밑에 컴포넌트들이 스토어에 연결되기 쉽게 만들어준다.
ㄴ. Connect()
- 컴포넌트가 Application 상태를 업데이트 받고 싶으면 connect() 를 이용해서 컴포넌트를 감싸주면 된다.
- connect()가 셀렉터를 이용해서 필요한 모든 연결을 만들어 준다.
ㄷ. Selector (셀렉터)
- Application 상태안의 어느 부분이 컴포넌트에 props로써 필요한 건지 지정한다.
- 사용자 설정 함수
6. 루트 컴포넌트
- 모든 팀이 일을 하도록 한다.
- 스토어 생성
- 무슨 Reducer를 사용할 지 알려준다.
- 뷰 레이어 바인딩과 뷰를 불러온다.
진행 순서
1. 스토어를 준비한다.
: 루트 컴포넌트는 createStore()를 이용해서 스토어를 생성한다. 이 후에 무든 Reducer를 사용할 지 알려준다.
루트 컴포넌트는 이미 필요한 모든 리듀서를 가지고 있고 CombineReducers()를 이용해서 다수의 리듀서를 하나로 묶는다.
2. 스토어와 컴포넌트 사이의 커뮤니케이션을 준비한다.
: 루트 컴포넌트는 provider component로서 서브 컴포넌트를 감싸고 스토어와 공급 컴포넌트 사이를 연결한다.
provider component는 컴포넌트 업데이트를 위해 네트워크를 형성한다.
영민한 컴포넌트는 connect()로 네트워크에 연결하고 이를 통해 상태 업데이트를 받을 수 있게 만든다.
3. 액션 콜백 준비
: 우직한 컴포넌트가 액션과 쉽게 일할 수 있게 영민한 컴포넌트는 buildActionCreators()로 액션 콜백을 준비한다.
이 콜백은 우직한 컴포넌트에 넘겨줄 수 있다.
액션은 포멧이 바뀐뒤 자동으로 보내지게 된다.
데이터 흐름
1. View가 Action을 요청한다. / 액션 생성자는 포멧을 변환한 뒤 돌려준다.
2. buildActionCreators() 가 준비 과정에서 사용되었으면 자동으로 액션이 보내진다.
그게 아니라면 View가 직접 Action을 보내야한다.
(View --> (action) --> Store --> (상태 트리 action) --> 루트리듀서)
3. Store가 Action을 받는다
현재 Application 상태트리와 액션을 루트 리듀서에게 전달한다.
4. 루트 리듀서는 상태트리를 조각으로 나눈 뒤 서브 리듀서에게 상태 조각을 넘겨준다.
5. 서브 리듀서들은 받은 상태조각들을 복사한 뒤 복사본을 Action에 맞게 변경하다.
이 후에 루트 리듀서에게 변경된 복사본을 돌려준다.
6. 루트 리듀서는 넘겨받은 변경된 상태조각 복사본들을 한 데 모아 상태트리로 만든다.
만든 이 후에는 Store에게 전달하고 Store는 이 전 상태트리와 변경된 상태트리를 교체한다.
7. 스토어는 뷰 레이어 바인딩에게 어플리케이션의 상태가 변경되었다고 알린다.
8. 뷰 레이어 바인딩(뷰 레이어)은 Store에게 새로운 상태를 보내달라고 요청한다.
9. 스토어는 변경된 상태를 뷰 레이어에게 전달하고 뷰 레이어는 변경된 상태를 뷰에게 전달하여
화면을 업데이트 하도록 한다.
( connect()을 통해서 컴포넌트들을 감싸고 있고 이를 통해 하위 컴포넌트들이 스토어에 연결 되기 쉽게 구조화 된다. )
이상 Redux의 개념에 대해서 소개해드렸고 관련된 추가 정의된 사항은 아래 링크를 따라가시면 됩니다!
http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/
다음에는 코드로 적용해서 보여드리겠습니다.
감사합니다.
'React.js' 카테고리의 다른 글
React 기본개념 (0) | 2023.01.17 |
---|---|
create-react-app (0) | 2018.03.06 |
FLUX 소개 (0) | 2018.03.01 |
keyEvent / ref / LifeCycle (0) | 2018.02.23 |
주소록 추가 파트 * Immutability helper 심화 * (0) | 2018.02.16 |