Redux는 상태(state) 관리 프로그램으로 사실 React에 속한 프로그램이 아니다. Redux는 React 없이도 사용할 수 있는 상태 관련 라이브러리이다.
Redux의 세 가지 원칙
1. Single source of truth
- 동일한 데이터는 항상 같은 store에서 가지고 온다.
2. State is read-only
- state는 직접 수정할 수 없고 Action으로 새로운 state를 생성하는 방식으로 사용한다.
3. Changes are made with pure functions
- Reducer는 순수 함수어야 불변성을 지킬 수 있다.
Redux는 크게 Action, Dispatch, Store, Reducer 이 네 가지를 유기적으로 연결해 상태를 관리한다.
Action
Action은 (상태(state)에) 어떤 행동을 취할지 정의하는 객체이다.
예)
{type: ‘ADD_TO_CART’, payload: request}
//type은 필수로 입력해야 한다.
상태(state)를 직접 바꾸는게 아니라 Action을 사용해 변화시키는 것으로 직관적으로 이해하기 쉽고 에러를 찾아내기 쉬워진다.
Dispatch
Dispatch는 Action을 전달해 Reducer를 호출하는 메소드이다. Action이라는 화물을 Reducer라는 목적지에 보내주는 택배차량 역할이라고 생각하자. Dispatch의 전달인자로 Action이 전달된다. 그리고 Reducer를 호출해 state의 값을 바꾸게 된다. 리액트에는 Dispatch를 사용하는 두 가지 방법이 있는데 자세한 것은 아래 Redux hooks를 참고하자.
Store
Store는 네이티브 리액트에서 class component의 state와 같은 state를 관리하는 저장소다. state와의 차이점은 Store는 오직 하나뿐이며 상하관계와 상관없이 여러 컴포넌트에서 접근이 가능하다.
//createStore()의 템플릿은 createStore(reducer, [preloadedState], [enhancer])이다.
//createStore에는 reducer가 필수로 들어가고, 미리 만들어진 state와 enhancer는
//옵션이므로 넣어도 되고 빼도 된다.
const store = createStore(rootReducer);
//위 코드에서는 createStore 메소드로 스토어를 만들 때 스토어와 rootReducer라는 리듀서와 연결하고 있다.
const rootReducer = combineReducers({
itemReducer,
notificationReducer
});
//여러 리듀서를 combineReducers()를 이용해서 하나로 묶어 사용 가능하다.
createStore 메소드를 이용해 Reducer와 연결해 Store를 생성한다. 하나의 Reducer 뿐만 아니라 다른 Reducer의 조합을 인자로 넣어 Store를 생성할 수도 있다.
Reducer
Reducer는 현재 Store가 가지고 있는 state와 Action을 합쳐 새로운 state를 만들어내는 함수이다(이 함수는 순수함수여야 한다).
Reducer의 파라미터로 받은 action으로 state를 새로 만들어낸다. 여러 Action들을 처리하기 위해 switch나 if문 같은 분기를 사용한다.
예)
//예시1
const itemReducer = (state = initialState, action) => {
switch(action.type) { //switch로 표현했지만 if를 써도 무방하다.
case ADD_TO_CART:
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload]
})
default:
return state;
}
}
//예시2
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload]
})
break;
case REMOVE_FROM_CART:
return Object.assign({}, state, {
cartItems: state.cartItems.filter(el => el.itemId !== action.payload.itemId)
})
break;
case SET_QUANTITY:
let idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId);
state.cartItems.splice(idx, 1);
state.cartItems.splice(idx, 0, {itemId: action.payload.itemId,
quantity: action.payload.quantity})
return state;
break;
default:
return state;
}
}
//참고 : assign(target, ...sources) 함수는 타겟이 되는 첫 인자에
//이어지는 인자들인 sources를 차례대로 덮어씌운다.
위 예제에서 Object.assign으로 새로운 객체를 만들어 리턴했다. 왜냐? Redux에서 state를 업데이트 할 때는 immutale한 방식으로 변경해야 된다는 규칙 때문이다. 이러한 방식으로 state를 변경하면 변경된 state를 로그로 남길 수 있는 장점이 있기에 이런 규칙을 따른다.
참고로, 여러 Reducer를 combineReducers 메소드를 사용해 하나로 합칠 수도 있다.
이상으로 Action, Reducer, Dispatch, Store라는 개념들을 짚어보았다. 개념을 알았으니 실제로 이것들을 연결해 사용해야 하는데, 연결(connect)하는 방법은 두 가지가 존재한다.
1. connect parameter를 통해 mapStateToProps, mapDispatchToprops등의 메소드를 이용하는 방법
2. Redux hooks를 이용해 useSelector(), useDispatch()등을 이용하는 방법
기존에는 첫 번째 방법으로 Redux를 사용했다. 고차 컴포넌트인 connect()로 컴포넌트를 감싼 후 mapStateToProps, mapDispatchToprops를 작성해야만 store에 접근이 가능했다.
하지만 그 후 두 번째 방법인 hooks를 지원하면서 간결하고 재사용하기 유리하게 코드를 작성할 수 있게 되었다. 다음으로는 첫 번째 방법 말고 두 번째 방법에 대해 설명해 보겠다.
Redux hooks
중요 - hooks를 사용할 때 루트 컴포넌트를 <Provider store= {store}>로 감싸야 한다!
그래야 스토어를 어디에서도 접근할 수 있기 때문이다.
예)
const store = createStore(rootReducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
useSelector()
useSelector()는 컴포넌트와 state를 연결한다. 컴포넌트에서 useSelector 메소드를 사용해 store에 존재하는 state에 접근이 가능해진다.
useDispatch()
useDispatch()는 Action 객체를 Reducer로 전달해주는 메소드다. 예를 들어 클릭 이벤트가 일어나는 컴포넌트에서 useDispatch 메소드를 사용해 store의 state를 변경한다.
'HTML, DOM, Node.js' 카테고리의 다른 글
이미지 업로드 시 미리보기 만들기 - React (0) | 2021.02.09 |
---|---|
Form submission canceled because the form is not connected Error- React (0) | 2021.02.09 |
Hooks를 위한 eslint 플러그인 설치법 - React (0) | 2021.01.07 |
Hooks - React (0) | 2021.01.07 |
리액트에서 html 폼을 사용하는 법(링크) - React (0) | 2021.01.05 |