리덕스란?
- 가장 많이 사용하는 리액트 상태관리 라이브러리
- 리덕스를 사용하면 컴포넌트의 상태 업데이트 로직을 다른 파일로 분리시켜 더욱 효율적으로 관리할 수 있다.
- 전역 상태를 관리할 때 굉장히 효과적. 단순히 전역상태 관리만 한다면 Context API를 사용하는 것으로도 충분. 하지만 프로젝트 규모가 클 경우 리덕스를 사용하는 것이 좋다. 왜냐하면 리덕스를 사용하면 상태를 더욱 체계적으로 관리할 수 있고 유지보수성을 높여주고 작업 효울도 극대화 시켜주기 때문이다.
- 추가로 편리한 개발자 도구를 지원하며 미들웨어라는 기능을 제공하여 비동이 작업을 효율적으로 관리할 수 있게 해준다.
리덕스 개념 미리 정리하기
액션(action)
액션 생성 함수(action creator)
리듀서(reducer)
스토어(store)
- 프로젝트에 리덕스를 적용하기 위한 스토어를 만든다. 하나의 프로젝트는 단 하나의 스토어만 가질 수 있다. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장 함수를 지닌다.
- 스토어는 리듀서 함수로 생성된다. 리덕스 스토어는 액션이 디스패치 될 때마다 루트 리듀서를 실행시킨다.
디스패치(dispatch)
- 디스패치는 스토어 내장 함수 중 하나로 액션을 발생 시키는 역할을 한다. 이 함수는
dispatch(action)
형태로 액션 객체를 파라미터로 넣어서 호출한다.
- 이 함수가 호출되면 리듀서 함수를 실행시켜 새로운 상태를 만들어준다. 상태를 변경하는 유일한 방법은 디스패치 함수를 사용하는 것이다.
구독(subscribe)
바닐라 자바스크립트로 리덕스 사용하기
- 리덕스는 리액트에 종속되는 라이브러리가 아니다. 리액트에서 사용하려고 만들어졌지만 다른 UI 라이브러리/프레임워크와 함께 사용할 수 있다.
- 예제를 통해 바닐라 자바스크립트 환경에서 리덕스를 사용하여 리덕스의 핵심 기능화 작동원리를 알아보자.
개발환경 세팅
- 리액트 없이 번들링하기 위해 Parcel를 번들링 툴로 사용
mkdir vanilla-redux
cd vanilla-redux
npm init -y
npm i -g parcel-bundler
npm i redux
코드
import { createStore } from "redux";
const divToggle = document.querySelector('.toggle');
const counter = document.querySelector('h1');
const btnIncrease = document.querySelector('#increase');
const btnDecrease = document.querySelector('#decrease');
const TOGGLE_SWITCH = 'TOGGLE_SWITCH',
INCREASE = 'INCREASE',
DECREASE = 'DECREASE';
const toggleSwitch = () => ({type: TOGGLE_SWITCH});
const increase = difference => ({type: INCREASE, difference});
const decrease = () => ({type: DECREASE});
const initialState = {
toggle: false,
counter: 0
};
function reducer(state = initialState, action) {
switch(action.type) {
case TOGGLE_SWITCH :
return {
...state,
toggle: !state.toggle
}
case INCREASE :
return {
...state,
counter: state.counter + action.difference
}
case DECREASE :
return {
...state,
counter: state.counter -1
}
default :
return state;
}
}
const store = createStore(reducer);
const render = () => {
const state = store.getState();
if (state.toggle) {
divToggle.classList.add('active');
} else {
divToggle.classList.remove('active');
}
counter.innerText = state.counter;
}
render();
store.subscribe(render);
divToggle.onclick = () => {
store.dispatch(toggleSwitch());
}
btnIncrease.onclick = () => {
store.dispatch(increase(1));
}
btnDecrease.onclick = () => {
store.dispatch(decrease());
}
리덕스 데이터 흐름
초기 생성
- 루트 리듀서 함수를 사용하여 리덕스 저장소 생성
- 저장소가 루트 리듀서 함수를 한번 호출하고 반환값을 초기 상태값으로 저장한다.
- UI가 처음 렌더링 될 때, UI 컴포넌트는 리덕스 저장소의 현재 상태에 접근한다. 그리고 그 데이터를 사용하여 화면을 그린다. 컴포넌트들은 또한 미래 스토어 변경을 구독하여 상태 변경을 알 수 있다.
업데이트
- 클릭 같은 이벤트 발생
- 앱은
dispatch({type: 'counter/increment'})
형식으로 Redux 스토어에게 액션을 보냄
- 스토어는 이전 상태와 현재 상태로 리듀서 함수를 작동 시킴. 그리고 반환 값을 새 상태로 저장한다.
- 스토어는 구독되고 있는 UI의 모든 부분에 스토어가 변경되었음을 알린다.
- 스토어의 데이터가 필요한 각 UI 컴포넌트는 필요는 상태의 일부가 변경되었는지 확인한다.
- 데이터 변경을 감지한 각 컴포넌트는 새로운 데이터로 리렌더링하여 사용자에게 새로운 화면을 보여준다.
리덕스 3가지 규칙
1. 단일 스토어
- 하나의 애플리케이션에 하나의 스토어를 사용하는 것이 좋다. 여러 개의 스토어를 만들 수 있지만 상태관리가 복잡해 질 수 있기 때문에 권장하지 않는다.
2. 읽기 전용 상태
- 리덕스틑 상태 읽기 전용이다.
setState
처럼 상태를 업데이트할 때 기존 객체를 건드리지 않고 새로운 객체를 생성해야한다.
- 리덕스에서 불변성을 유지해야하는 이유는 내부적으로 데이터가 변경되는 것을 감지하기 위해 얕은 비교 검사를 하기 때문이다.
3. 리듀서는 순수함수
- 리듀서는 순수한 함수여야 하며, 순수함수는 다음 조건을 만족시킨다.
- 리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.
- 파라미터 외에 값에는 의존하면 안된다.
- 이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어 반환한다.
- 똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야한다.
- 순수 함수란 동일한 입력을 받았을 때 언제나 동일한 출력을 내는 함수를 말한다. 클릭 시 배경이 랜덤으로 바뀌는 로직을 작성한다면, 함수의 로직 내에서 랜덤값을 생성해야한다. 랜덤 값을 생성한다는 뜻은 결국 매번 출력 값이 바뀐다는 뜻이기 때문에 순수하지 못한 함수다. 네트워트 요청을 하는 작업도 순수하지 못한 로직 중 하나다.