채용공고를 훅훅 보다보면, 우대사항이나 필수사항에 항상 들어가는 것이다. 누군가는 항상 말했다. redux는 필수라고~~ 기업인턴을 가서도 항상 들었던 말이었다. 사용해보고싶지만, 선뜻 어렵고 복잡하다는 소리를 많이 들어서 시작도 하기전에 겁부터 먹고 하니, 머리에 잘 안들어오더라. 하지만, 공부는 해야되니까!!
시작해보자~~
항상 우리는 어떤 프로젝트를 만들때, 사용할 언어 프레임워크 등등을 정하고 시작을 하는데, 그 전에 왜 꼭 그것을 사용해야 하는지가 첫번째이자 그리고 제일 중요한 문제이다.!
그래서~! 리덕스는 왜 사용해야 되는건데? 굳이 써야되나?? (쓰는게 좋을껄?? ㅋㅋ)
리덕스, 상태 관리(Managing State) 공식문서를 보면 이유가 친절히 설명이 되어 있다. 한 4가지 정도로 설명이 되어 있는데, 쨋든,
리덕스는 중앙집중국??
난 중앙난방이라고 비유하면서 이해했다. 이게 무슨말이냐하면, redux 는 store 라는 저장소를 만들수 있는데, 이 저장소 안에서 state를 관리하면서, 내가 원하는 컴포넌트로 쭉쭉 뿌려줄수가 있다는 것이다. 중앙난방에서 모든 아파트의 온돌을 관리하듯, store에서 전체 state를 관리 한다는 뜻이다.
그리고 다른 이유로는 props 지옥에서 탈출할 수 있다~~ 🙌🙌🙌🙌
리액트는 컴포넌트화가 되어있다보니, 최상위 부모에서 사용한 것을 자식의 자식의 자식의 자식에서 사용한다면 props 를 내리고 내리고 내리고 반복하게 된다.
이런 식으로 하다보면 헷갈릴 수도 있고, 실수가 나올 수도 있다. 이 것을 redux가 좀더 쉽게 도와준다. 왜냐하면 store에서 따로 관리를 하다보니 어느 위치에 있든 상관없이 단 한번에 상태를 받을 수 있기 때문이다.
위의 그림처럼 뿌려주는 형식이다.
만약 리덕스를 사용하지 않으면, 동작 하나하나에 다른 코드들과의 종속관계 즉, 연결고리가 많을 경우, 하나의 코드가 지워지면 에러를 발생키기게 된다.
리덕스를 사용하게 될 경우, 하나의 store에서 state를 뿌려주는 식으로 동작하기 때문에, 서로의 의존관계가 사라지게 됩니다. 따라서, 각자의 컴포넌트에 집중해서 작업을 할 수 있습니다. 즉, 디커플링.
Redux 스토어(store)는 애플리케이션의 상태를 관리하고, getState(), dispatch(), subscribe()
같은 메서드를 제공합니다.
import { createStore } from 'redux'
// Redux 스토어 생성
// - 리듀서 함수를 전달 받음
const store = createStore(reducer)
스토어는 애플리케이션의 상태를 나타내는 많은 key: value 쌍으로 구성된 정보를 가진 하나의 큰 Javascript 객체입니다. React 컴포넌트에 분산디어 있는 상태 객체와 달리 스토어는 하나만 존재합니다. 스토어는 애플리케이션에 상태를 제공하며 상태가 업데이트 되면 뷰(UI)가 다시 그려집니다.
Redux 스토어에서 관리하는 상태(데이터) 입니다.
// 상태
// - 일반적으로 state 또는 initState 이름으로 설정
// - 상태는 리듀서(함수)의 첫번째 인자로 전달 됨
// counter
const state = 0
// todos
const initState = []
상태 트리(state tree)는 '불변 상태(Immutable State). 즉 순수한 상태(건드리지말라!)를 가져야 한다. '
스토어에 등록된 상태 정보는
.getState()
메서드를 사용해 가져올 수 있다.
store,getState()
액션은 애플리케이션에서상태 변경을 설명하는 정보
를 스토어로 보내는 javascript 객체로 redux에 알려(dispatch) 변화를 이끌어 냅니다. 상태 값을 변경(교체) 할 경우, 교체할 상태 값(payload)을 리듀서(함수)에 보낼 수 있다.
// '카운트 증가' 상태 변경을 설명하는 액션
const increaseCountAction = { type: "INCREASE_COUNT"}
const decreaseCountAction = { type: "DECREASE_COUNT"}
const resetCountAction = { type: "DECREASE_COUNT", payload: 0}
애플리케이션 상태 트리를 변경하는 유이한 방법은 "액션을 보내는 것" 이다.
store.dispatch(action)
액션타입을 별도 관리하는 파일을 만들어 상수(constant)로 관리하는 것이 유지보수하기 좋다.
export const INCREASE_COUNT = 'INCREASE_COUNT' export const DECREASE_COUNT = 'DECREASE_COUNT' export const RESET_COUNT = 'RESET_COUNT'
import { INCREASE_COUNT, DECREASE_COUNT, RESET_COUNT} from './actionTypes'
export const increaseCountAction = () => ({ type: INCREASE_COUNT })
export const decreaseCountAction = () => ({ type: DECREASE_COUNT })
export const resetCountAction = () => ({ type: RESET_COUNT_COUNT, payload: 0 })
모든 Redux 애플리케이션은 공통점이 있다. 바로 리듀서(reducer)를 구현해야 한다는 점이다.
리듀서란? '애플리케이션 상태를 교체하는 함수'를 말합니다. '이전 상태(prevState)' 를 '새로운 상태(state)' 로 교체 한다.
const reducer = (prevState = initState, action) {
switch(action, type) {
case INCREASE_COUNT:
return prevState + 1
case DECREASE_COUNT:
return prevState - 1
default:
return prevState
리듀서는 상태와 액션을 전달 받아 '이전 상태' 를 '다음 상태' 로 교체한 후 반환한다. 즉, 리듀서는 '순수한 함수' 여야 한다.
리듀서 = (상태, 액션) {
// 액션 타입 분석
// 이전 상태 -> 다음 상태로 교체
// 다름 상태 변환
순수한 함수란?
const square = x => x * x
const squareAll = item => item.map(sqauare)
리듀서(함수)는 순수해야 합니다. 순수함을 잃어버리면 사이트 이팩트(부작용)를 발생시킬 수 있습니다.
- 전달 받은 매개변수 state, action에 변형을 가하면 안됩니다.
- 네트워킹(API 호출 <- 비동기 통신) 또는 라우팅을 변경하면 안됩니다.
반드시 반환 값은 새로운 상태(state) 입니다.
애플리케이션 상태 변경을 구독(subscribe, 감지) 하여 상태가 업데이트 되면 등록된 리스너를 실행시킵니다.
store.subscribe(render)
.subscribe()
메서드는 구독을 취소할 수 있는 unscribe 함수를 반환합니다.
const unsubscribe = store.subscribe(() => console.log(`상태 변경 감지: ${store.getState()}`))
unsubscribe 함수를 실행하면 상태가 업데이트 되어도 UI를 업데이트 하지 않습니다.
unsubscribe() // 구독취소
Redux 작동방식
let store = Redux.createStore(reducer);
function reducer(state, action){ // state는 이전 값, action은 바꿔주는 역할을 한다.
if(state === undefined){
// state의 값이 아직 세팅되지 않았으면 초기값은 (초기화를 위해) undefined 가 된다.
}
}
let newState;
if(action.type === "CHANGE_COLOR") {
newState = Object.assign({}, state, {color:'red'})
}
return newState;
let state = store.getState()