리액트는 상태(state), 속성(props)을 이용한 컴포넌트 단위 개발 아키텍쳐
리덕스는 상태 관련 라이브러리
cf.react 관련 라이브러리가 아니라 무관한 상태 관련 라이브러리다
컴포넌트와 상태를 분리해준다
어떻게 상태 변경 로직을 컴포넌트로부터 분리할 수 있는지 확인해보자![원리, 구조, 설계 이해]
이를 통해 표현에 집중한 단순한 함수 컴포넌트를 만들 수 있다
상태 관리 라이브러리가 왜 필요한지 이해할 수 있다.
: state는 리액트의 컴포넌트 안에서 관리
: 그러므로 자식 컴포넌트에서 다른 자식에게 데이터를 전달할 때 부모에서 관리해야
: 그럼 구조가 복잡해질수록 관리하기 어렵다
: 그러므로 store이라는 공간 하나에서 모든 state를 저장해서 사용
Redux (혹은 Flux Pattern)에서 사용하는 Action, Reducer 그리고 Store의 의미와 특징을 이해할 수 있다.
: 1.store : 컴포넌트와 별개의 공간, 상태과 관리되는 오직 하나의 공간
방법1) map state props
방법 2) redux hooks
상태를 예측 가능하게 만들어준다
: reducer는 순수함수이므로
유지보수
: 복잡한 컴포넌트에서 props등을 이용해서 state 이용하면 굉장히 추적하기 어렵다
디버깅 유리하다 : action, state log 기록 시
: redux dev toll
: 개발자 도구의 redux 탭을 이용하면
action, (데이터 객체)
state 등을 추적할 수 있다
순수함수를 이용하고 있기 때문에 테스트를 붙이기 쉽다
store를 이용한 상태 관리 라이브러리
: state container for JS applications
state : 컴포넌트 내에서 관리
=> this.state
=> 앱에서 필요한 상태들을 담아두는 공간
하지만 형제 컴포넌트들 간에 데이터를 주고 받을 때는 문제 발생
그러므로 부모 컴포넌트에 상태를 담아두고 자식 컴포넌트가 받아서 사용한다 (간접적)
만약 자식이 너무 많고 상태가 너무 많으면 복잡해진다
그러므로 상태 관리 라이브러리인 리덕스를 이용한다
class Clock extends React.Component{
constructor(props){
super(props);
this.state = {data: new Date()};
}
render(){
return(
)
}
}
상태가 관리되는 오직 하나의 공간
app에서 필요한 state들을 저장해두고 컴포넌트에서 필요할 때마다 store에 접근해서 가져온다
자바스크립트 객체
타입을 비롯한 다양한 데이터들이 담긴다
스토어에게 앱의 데이터를 운반하는 역할을 한다
타입은 필수!
현재 상태와 Action을 이용해 다음 상태를 만들어낸다
Action
Dispatch(action 객체가 현재 상태와 reducer를 이용해 new state를 만들어낸다)
dispatch는 action객체 -> dispatch메소드 -> reducer호출 -> 새로운 state값 생성
데이터가 한 방향으로만 흘러야 하기 때문에
reducer는 순수 함수
-> 상태를 예측 가능하게 만들어준다
유지보수
디버깅에 유리
데스트를 붙이기 쉽다
redux devtool
직접 다운로드
개발자 도구 중 redux 탭 사용
뒤로가기도 편하다
상품 리스트 페이지
장바구니 페이지
App.js에서 state를 관리할 때 가장 하단에 있는 컴포넌트에서 사용하려면 최하위 컴포넌트까지 props drilling을 여러 차례 해야할 수 있음
lifting state up, props drilling
누구나 접근할 수 있는 store가 있으면 편하게 state를 이용할 수 있다
Cmarket Shopping App은 Create React App으로 만든 리액트 앱에 리덕스를 붙인 구조입니다.
페이지
아이템 리스트 페이지(ItemListContainer)
장바구니 페이지(ShoppingCart)
Store
initial state : 전체 아이템 목록(items), 장바구니 목록(cartItems)
여러 컴포넌트들에서 Store(state)에 접근
: redux에서 제공하는 hooks, useDispatch, useSelector를 사용합니다
Action, Reducer, Dispatch, Store
어떻게 유기적으로 연결되어 있는지 배우실 수 있습니다.
Action
Action은 말 그대로 어떤 액션을 취할 것인지 정의해 놓은 객체입니다.
{ type: ‘ADD_TO_CART’, payload: request }
이렇게 모든 변화를 action을 통해 취하는 것은
우리가 만드는 앱에서 무슨 일이 일어나고 있는지 직관적으로 알기 쉽게 하는 역할을 합니다.
actions > index.js 파일에서는 action들을 구성
action을 구성하는 파일 필요
장바구니를 구현하기 위해서 필요한 액션들이 무엇이 있을지 고민한 후 코드를 작성해 보세요.
Dispatch
Dispatch는 Action을 전달하는 메서드입니다.
dispatch의 전달인자로 Action 객체가 전달됩니다.
그리고 Reducer를 호출해 state의 값을 바꾸는 역할을 합니다.
dispatch(action) => reducer => store의 state 변경
Store
말 그대로 state가 관리되는 **오직 하나뿐인 저장소의 역할을 합니다.
Redux 앱의 state가 저장되어 있는 공간이죠.
다음은 createStore 메서드를 활용해 reducer를 연결하는 방법인데요, createStore와 더불어 다른 리듀서의 조합을 인자로 넣어서 스토어를 생성할 수 있습니다.
(실제 소스 코드에서는 미들웨어와 Redux devtools 지원을 위해 두 번째 인자에 추가적인 내용이 들어가 있습니다.)
const store = createStore(rootReducer);
store > store.js 파일에서 createStore 메소드를 활용해 rootReducer를 연결해 주고 있습니다.
Reducer
Reducer 는 현재의 state와 Action을 이용해서 새로운 state를 만들어 내는 pure function 입니다. 또한 보이는 코드는 쇼핑몰에서 크게 볼 수 있는 장바구니 추가 액션에 대한 코드입니다.
const itemReducer = (state = initialState, action) => {
//default state
switch (action.type) {
//action.type가 ADD_TO_CART면 아래 내용을 넣어서 객체를 만든다
case ADD_TO_CART:
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload]
})
default:
return state;
}
}
보통 위와 같은 모양으로 구성됩니다. 위의 예시에서는 switch 문을 통해서 코드를 작성했지만 if 문으로 작성해도 무방합니다.
Reducer의 Immutability(불변성)
Reducer 함수를 작성할 때 주의해야 할 점이 있습니다. 바로 Redux의 state 업데이트는 immutable한 방식으로 변경해야 한다는 것인데요.
-> Redux의 장점 중 하나인 변경된 state를 로그로 남기기 위해서 꼭 필요한 작업입니다.
왜 immutable 한 방식으로 바꾸어야 하는지 잘 모르겠다면 React life cycle 키워드로 검색해 보는 것을 권장합니다.
(React에서 state를 변경하기 위해서는 this.state에 바로 할당하는 것이 아닌 this.setState를 통해 state를 변경해 주어야 했죠?)
이 룰에 대해 자세하게 다룬 블로그 글도 시간이 된다면 한번 읽어보는 것을 추천합니다. (React를 잘 사용하기 위해서도 꼭 알아야 하는 내용들이 있어요.)
출처: https://daveceddia.com/react-redux-immutability-guide/
그렇다면 immutable한 방식으로 state를 변경하기 위해서는 어떻게 코드를 작성해야 할까요? 위의 itemReducer 예제 코드에서 Object.assign을 통해 새로운 객체를 만들어 리턴하는 것을 통해 힌트를 얻을 수 있습니다.
그렇다면 reducers > index.js 파일에서 rootReducer를 구성할 차례입니다.
Reducer은 combineReducers 메소드를 통해 여러 가지의 Reducer들을 하나로 합칠 수 있습니다.
여기까지 Action, Reducer, Dispatch, Store 개념들을 코드로 구성하는 것은 완료했습니다. 그렇다면 이제 이 개념들을 연결시켜주어야 할 텐데요. connect 할 수 있는 방법은 두 가지가 있습니다.
Redux 공식문서
https://react-redux.js.org/api/hooks
공식 문서를 통해서도 확인하실 수 있듯이 connect parameter를 통해 mapStateToProps, mapDispatchToProps 등의 메서드를 이용하는 방법과 Redux hooks를 이용하는 방법이 있습니다. 이 중에서 Redux hooks가 보다 최근에 나온 방법이고 비교적 사용하기 쉽기 때문에 이번 스프린트에서는 Redux hooks를 이용해서 진행하도록 하겠습니다.
Redux hooks에서는 크게 useSelector(), useDispatch() 이 2가지의 메서드를 기억하시면 됩니다.
useSelector()
먼저 useSelector()는 컴포넌트와 state를 연결하는 역할을 합니다.
컴포넌트에서 useSelector 메서드를 통해 store의 state에 접근할 수 있는 것이죠.
useSelector의 전달인자로는 콜백 함수를 받으며 콜백 함수의 전달인자로는 state 값이 들어갑니다.
자세한 사용법은 공식 문서의 useSelector examples 를 참고하세요.
useDispatch()
useDispatch()는 Action 객체를 Reducer로 전달해 주는 메서드입니다. Action 이 일어날만한 곳은 클릭 등의 이벤트가 일어나는 컴포넌트겠죠.
어떤 컴포넌트에서 useDispatch를 이용해 Action을 Reducer로 전달해 줄 수 있을지 고민해 보세요. 그런 다음 공식 문서의 useDispatch() examples를 통해 dispatch 메서드에 전달인자로 Action이 어떻게 전달되는지 확인하고 코드를 작성해 보세요.