드디어 Redux를 공부해본다... 그토록 어렵다고 들었던...
그래서 일단은 시작은 조심스럽게..해보쟈 ... 😣
Redux란?
- 공식문서에서는 자바스크립트 앱을 위한 예측가능한 컨테이너 라고 소개한다.
: 단방향 데이터 흐름을 이용해 예측 가능하고, 일관적인 상태 컨테이너의 역할을 제공하는 라이브러리
: Flux 패턴에 영감을 받은 Dan Abramov 가 개발함
: React 또는 다른 View 라이브러리들과 함께 사용할 수 있다.
: Flux패턴과 혼동이될 수 있는데, Flux는 디자인패턴으로 일종의 실체가 없는 개념이다.
이러한 패턴을 쉽게 적용할 수 있도록 자바스크립트로 구현한 구현체가 Redux 이다.
Redux의 3가지 원칙
1. 진실은 하나의 소스로부터
2. 상태는 읽기 전용이다
3. 변화는 순수함수로 작성되어야 한다
무슨소리일까..하겠지만...
이 원칙들에 대해 깊게 파해쳐 보쟈!!!!
1. 진실은 하나의 소스로부터
- 애플리케이션의 모든 상태는 (state) 하나의 저장소 (store)안에 하나의 객체 트리 구조로 저장되는데,
이 말은, 여러개의 저장소를 따로 관리하는것이 아니라, 단 하나의 자바스크립트 객체로만 데이터를 저장
=> 관리하기 쉽고, 변화를 예측하기 쉬워짐
(실제 store의 형태는 객체형태로 표현됨)
2. 상태는 읽기전용이다
- Redux Store의 상태는 읽기전용이다 : 즉, Store에 직접 접근해서 state를 수정할 수 없다.
그럼 상태를 수정하려면??
- Action 객체를 Dispatch를 통해서 전달하는 방법이 유일하다
마치 setState 처럼 Dispatch함수에 액션객체를 담아서 보낸다. 이렇게 상태를 업데이트하는 방식과 시점을 제한해서 상태가 언제 바뀌는지 예측 할 수 있고, 이렇게 업데이트를 제한하는 이유도 예측 가능하게 만들기 위함 이다.
: Redux에서는 이런식으로 상태를 업데이트 하는 방식과 시점을 제한해서 예측 가능성을 증가시킨다!
ex)
- store.dispatch 함수를 호출 하고, 인자로 하나의 객체를 넘기고 있다.
** 이때, 이 인자로 전달되는 객체가 Action 객체이다.
(Action객체에서는 의도를 'type' 프로퍼티를 이용해서 표현하고, 그 외 필요한 정보들을 다른 프로퍼티를 이용해 전달한다.
Dispatch가 호출된 순서대로 상태를 변경해서 변경된 순서와 내역을 쉽게 관리할 수 있다.
Dispatch가 전달하는 Action 객체는 평범한 자바스크립트 객체이며, 이 객체가 Store에 입력된 순서와 내용을 저장해두면 시간여행하듯(time travel) 이전의 상태값으로 돌아가 디버깅도 가능하다.
3. 변화는 순수함수로 작정되어야 한다
- Redux 에서 State의 유일한 변경방법은
Action 객체를 Dispatch를 통해 Store에 전달
하는 것인데,
이 때,
Store에서는 전달된 Action을 통해 State를 변경해야 하는데,
** 모든 변화들은 순수함수의 형태로 작성되어야 한다
그럼 도데체 순수함수가 뭘까????
: 순수함수는 사이드 이팩트가 없는 함수이며, 이러한 순수함수는 같은 Input에 대해 같은 Output을 보장할 수 있다.
사이드 이팩트 : 함수가 인자를 제외한 외부의 값을 읽어오거나 수정하는 등의 행위를 의미
ex)
- addOne : 순수함수 => 상태의 변화를 예측 가능하게 할 수 있음
- addRandom : 순수하지 않은 함수
num 이외에 Math.random이라는 외부의 값을 읽어오고 있음 => 함수 호출시 결과값이 매번 다르게 나올 수 있음 + 외부의 상황에 영향을 받을 수 있음 => 함수의 동작결과를 예측하기 힘듦
Redux에서는 이렇게 state 변화를 책임지는 순수함수를 'Reducer' 라고 부른다
리듀서는 이전 상태값과 액션 객체를 입력으로 받아 다음 상태를 반환하는 순수 함수이다.
액션에 의해 상태가 어떻게 변하는지 통제하기 위해 리듀서 를 작성해야 한다.
이전 상태를 변경하는 대신 새로운 상태 객체를 생성해서 반환해야 한다.
순수 함수는 테스트 코드를 작성하기도 쉽다. 왜? 입력대로 출력하기 때문
그래서 리듀서는 같은 상태태값과 액션 객체를 입력하면 항상 똑같은 다음 상태값을 반환한다
=> 이전의 state와 action 객체를 Input으로 받고, 새로운 state를 만들어서 return 하는 함수이다.
ex) reducer예시
- 함수이다.
- 인자로 이전 state와 action을 받고 있다.
- 여기서처럼 이전 state 가 없을수도 있기에 인자의 기본값을 설정해 준다. (= store에서 state의 초기값이 된다)
- switch구문
: if문과 동일하게 분기처리를 하는 문법이다.
옆의 예시에서는 action.type (action 객체의 type)
프로퍼티에 따라서 분기처리를 하였고,
- type이 "ADD_TODO"인 문자열일때 : return[...state(기존의 state를 복사) ] 후 새로운 투두리스트 객체를 추가 하여 새로운 state를 만들어서 return 하고 있다.
- type이 "COMPLETE_TODO"일때, 특정 투두 리스트의 완료여부를 ture로 만들어서 return 해주고 있다.
- 마지막으로, action.type이 위 중 아무것도 아닐때 (default :return state) : 인자로 받은 state를 그대로 다시 return 해 주고 있다.
Redux에서 이러한 Reducer 함수를 이용해서 state를 변화시킨다.
Redux의 데이터 흐름을 잘 이해하기 위해서 그림과함께 설명된 것을 정리해 보았다.
개괄적인 데이터 흐름
<다이어그램 구성>
- UI : View
- Event handler : Dispatch
- Store 내부의 Reducer들
- State
1. 현재 잔액: 0원 , 유저가 10만원 입금 버튼을 클릭
2. Click Event 발생 -> Event Handler에게 전달
3. Event Handler에서 변화 발생을 위해 Action 객체를 만듦
4. Dispatch를 이용하여 Action 객체를 Store에 전달
5. Store에서는 Reducer함수를 이용하여 새로운 state를 만듦.
이 때, Action 객체는 전달받았으나 현재 state가 없으므로, store는 현재 state를 Reducer에 인자로 전달해줌
6. Reducer에서 현재 state와 action 객체를 통해 새로운 state를 만듦
= 현재 state 0 , Action 객체에 10 입금 => 새로운 state : 10
7. Store가 새로운 state로 설정 -> 변화된 사실을 View에 전달
8. View에서 state변화 인지 후 기존의 현재잔액: 0 -> 10 변경
그림으로 설명된것을 정리하니 개념이 좀 잡혔다!
Redux의 주요 개념
- View
: 화면에 보이는 요소 (유저가 사용하는 화면, 버튼 등 UI요소)
- Action & Action Creator
Action : 상태변화에 대한 의도를 표현하는 단순한 자바스크립트 객체
: Redux에서는 Action 객체의 의도를 표현하기 위해 type 이라는 프로퍼티를 필수적으로 가져야 한다
: type 외의 다른 프로퍼티는 상황에 따라 생략 가능
ex)
- 자바스크립트 객체
- type 의도 : "ADD_CART" (string)
- 이외 : payload 프로퍼티(추가적인 값)
Action Creator
: Action 객체를 만드는 함수
: 매번 Action 객체를 필요할때마다 만들어도 되지만, Action Creator 함수를이용하는 경우 중복을 제거하고, Action 을 만드는 과정에서 실수를 방지할 수 있는 이점이 있다.
(그래서 Action Creator 함수를 통해서 Action 을 만드는 방식을 권장함)
ex)
- 함수이면서 인자로 (item)을 받아서 Action 객체를 만들어서 return 하는중
- Dispatcher
: Action 객체를 Store의 Reducer에 전달하는 역할을 담당한다
: Redux 에서는 'store.dispatch()' 의 형태로 제공한다
**주의
Dispatch는 동기적으로 처리되도록 작성되었기에 비동기 Action (API호출 등) 을 처리할 수는 없다.
=> 비동기적인 처리가 필요한 경우 Middleware를 활용해야 한다
ex)
store.dispatch(addCart(payload))
: store.dispatch 형태의 함수로 제공
: 인자로 Action 객체를 넣어서 호출하면 이 Action 객체를 Reducer에 전달해 준다
: 여기서는 addCart 라는 Action Creator 함수를 통해서 Action을만들고, dispatch 함수의 인자로 전달하는 것이다.
- Reducer
: 이전 state와 Action 객체를 받아서 새로운 state를 리턴하는 순수함수
: Redux는 store가 하나이기 때문에 Reducer(store의 변화를 발생시키는) 도 하나로 만들어져야 한다.
여기서 주의할 점이
첫번째로 Reducer는 불변성을 지켜 업데이트 해야 함.
state 원본을 직접 수정하면 안됨.
React에서 setState로 그렇게 해왔듯 기존 값을 복사하고 새롭게 복사된 값을 덮어쓰는 방식으로 업데이트 해야 한다.
두번째는 Reducer 내부에서 비동기나 여타 순수하지 않은 Promise(), Math.random(), Date.now()같은 로직(Side Effect)을 처리하면 안된다.
** 모든 관심사가 한 함수에 합쳐지면 유지보수가 어렵다
=> Redux에서는 각각 로직을 관심사별로 분리해서 SliceReducer를 만들고 -> CombineReducer함수를 이용해서 RootReducer를 만드는 방식을 사용한다.
- Slice Reducer: 관심사에 따라 분리되어 있는 Reducer . 여러 Slice Reducer들이 결합(combineReducers)되어 Root Reducer를 구성한다.
- Root Reducer: createStore의 첫 번째 인자로 전달되는 함수. combineReducers 메서드로 관심사에 따라 분리되어 있는 Reducer들을 하나로 묶는다.
ex)
<SliceReducer 예시>
- cart에 대한 변화를 책임지고 있다
<CombineReducer 예시>
- combineReducer 함수를 호출하면서,
const RootReducer = combineReducer({cart, count});
위처럼 sliceReducer들을 객체형태로 인자로 전달하여 rootReducer를 만들어 주었다.
- Store
: Redux의 전체 state를 관리하는 하나의 객체
: Store객체를 직접 선언해서 만들 필요 X
이유 : 기존에 만들어둔 rootReducer를 통해서 store를 만들어주는 createStore라는 함수를 Redux에서 제공해 주고 있음=> 이를활용하여 store 객체를 만들어 준다.
Store는 Redux 앱 전체의 상태로 보통 깊게 중첩되어 있는 객체
모든 state의 저장소로store.getState()로 접근할 수 있다.
항상 직렬화(Serialization) 즉, JSON으로 변환할 수 있어야 하기 때문에 JSON으로 쉽게 변환할 수 없는 함수나 Promise들은 제외하는게 좋다.
만약, createStore를 import 하고 취소선이 보이면 redux github의 문서를 참고
- Middleware
: Action 이 Dispatch를 통해서 Reducer에 전달되기 전 중간처리를 담당하는 부분이다.
: Redux를 활용하기 위해서 필수적인 구성요소는 아니다
: Reducer로 전달되기 전 Middleware를 통해 중간처리를 할 수 있기때문에 Reducer로 전달되는 모든 Action 객체들에 대해서 로그를 기록하거나, 심화적으로 비동기 API 처리를 하고 다시 Action 객체를 Dispatch 하는 등의 동작을 처리할 수 있다.
: Redux와 함께 사용되는 Middleware 라이브러리는 redux-thunk(간단한 처리), redux-saga(복잡한 처리) 등이 있다.
비동기 API 호출 등 순수하지 않은 요청(Side Effect)을 처리하거나, Redux Store로 전달되는 Action 등을 로깅하는 장소이며 Reducer가 액션을 처리하기 전에 실행되는 함수.
Middleware를 설정하지 않으면 dispatch한 action은 동기적으로 곧바로 리듀서로 보내진다.
Redux의 데이터 흐름 (요약)
1. View에서 event 발생하면,
2. Action (변화를 일으키기 위해) 객체 생성
3. Dispatch로 Action 전달
4. (Middleware) : 만약 Middleware가 있다면 거쳐간 뒤
5. Reducer 에 전달되어 -> Store의 state 변경
6. 변경된 state가 UI 반영
redux 주요개념 예시 코드(reducer, store, dispatch, action)
// reducers/index.js
// reducer 예시 : (store, action) => newStore
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
// store생성 예시 - 애플리케이션 전체에서 하나만 존재
const store = createStore(reducer); // getState, subscribe, dispatch
// store조회 예시
const render = () => {
document.querySelector('.app').innerText = store.getState();
};
// view에서 일어난 이벤트에 Action을 실어 dispatch
document.addEventListener('click', () => {
store.dispatch({ type: 'INCREMENT' }); // action
});
// store에 업데이트가 일어났을 때 subscribe에 전달받은 함수를 실행
store.subscribe(render);
createStore 의 API 3가지 예시
createStore Store의 API는 getState, subscribe, dispatch 3가지 이다.
- getState는 state를 조회하는 역할
- subscribe는 변경사항을 구독하고 있다가 상태가 일부 변경 될 수 있을 때 마다 호출 된다.
- dispatch는 Action을 보내는 메서드이고, 상태 변경을 일으키기 위한 유일한 방법이다.
// createStore API 예시
// reducer를 받는 store
const createStoreFromScratch = (reducer) => {
let state;
let listeners = [];
// store의 state 를 조회하는 getState
const getState = () => state;
// state의 변화를 구독(감지)하는 subscribe
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
};
// reducer에 action을 전달하는 dispatch
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
};
dispatch({});
return { getState, subscribe, dispatch }; // store
};
흐.. Redux...너란녀석... 조금은 흐름..이해했다...
'프론트앤드 > [React]' 카테고리의 다른 글
[TypeScript] 공통컴포넌트 Nav 이용해보기 (0) | 2023.10.24 |
---|---|
[React] 디자인 패턴 (MVC, Flux) (0) | 2023.07.01 |
[React] Custom Hook (useInput) (0) | 2023.06.19 |
[React] React.js로 전화걸기 링크 제공 하기 (0) | 2023.04.22 |
[React] input 으로 프로필 이미지 업로드 하기 (0) | 2023.04.20 |