리덕스는 상태 관리 라이브러리 중 하나로, 현재까지 가장 많이 쓰이고 있다.
상태 관리란 UI와 UX에 맞게 데이터를 관리하거나, 서버와 주고 받는 데이터를 관리하는 것을 말한다.
간단한 프로젝트라면 괜찮겠지만, 복잡하고 크기가 큰 대형 프로젝트라면 상태 관리의 난도는 크게 올라간다. 리액트의 state 끌어올리기를 생각해보자. 한 컴포넌트의 함수를 props 형태로 다른 컴포넌트로 전달하고 또 그것을 props 형태로 또 다른 컴포넌트로 전달하고... 이런 복잡한 구조는 불필요한 props 전달로 유지보수 또는 props 추적을 힘들게하는 Props drilling을 야기한다.
상태 관리 라이브러리는 이러한 문제들을 해결하기 위해 고안되었다.
위의 그림은 기존 상태 관리 방식을 시각화한 것이다. 위 그림처럼 상태 변경이 한 번 일어났을 뿐인데 해당 데이터를 변경하기 위해 또 다른 상태 변경이 여러 번 일어나고 있다. 이러한 구조는 오류를 야기할 수 있으며, 발생한 오류를 잡아내기도 쉽지 않다.
그러나 리덕스는 Reducer와 Store를 통해 상태 변경 과정을 간소화하여 기존의 문제를 해결했다. 이것은 복잡한 상태 관리를 매우 효율적이고 간편하게 변경하여 오류 발생률을 낮추게 한다.
리덕스의 기본 용어에 대해서 알아보자.
스토어는 컴포넌트의 상태를 관리하는 저장소다. 하나의 프로젝트는 하나의 스토어만 가질 수 있다.
스토어의 상태를 변경하기 위해서는, 액션을 생성해야한다. 액션은 객체이며, 반드시 type을 가져야 한다. 액션 객체는 액션생성함수에 의해서 만들어진다.
리듀서는 현재 상태와 액션 객체를 받아 새로운 상태를 리턴하는 함수다.
디스패치는 스토어의 내장 함수 중 하나이며, 액션 객체를 넘겨줘 상태를 업데이트 시켜주는 역할을 한다.
스토어의 내장 함수 중 하나로, 리듀서가 호출될 때 서브스크라이브된 함수 및 객체를 호출한다.
아래의 그림으로 위의 용어들을 이해해보자.
UI가 처음 렌더링될 때, UI 컴포넌트는 리덕스 스토어의 상태에 접근하여 해당 상태를 렌더링한다.
이후 UI에서 상태가 변경되면, 앱은 디스패치를 실행해 액션을 일으킨다.
새로운 액션을 받은 스토어는 리듀서를 실행하고 리듀서를 통해 나온 값을 새로운 상태로 저장한다.
서브스크라이브된 UI은 상태 업데이트로 변경된 데이터를 새롭게 렌더링한다.
리덕스를 사용하기 위해서는 먼저 리덕스를 설치해야 한다.
$ npm install redux
리액트 환경이라면 추가로 react-redux
를 설치해야한다.
$ npm install redux react-redux
리덕스 설치가 완료되었다면, 스토어를 만들어 주자. 스토어는 createStore()
를 통해 만들 수 있다.
import { createStore } from 'redux';
그러나 리덕스 4버전 이상일 경우 아래와 같은 경고 메세지가 나타난다.
경고 메세지는 스토어를 만들때 리덕스의 createStore()
를 사용하기보단, 리덕스 툴킷의 configureStore()
를 사용할 것을 요구한다.
리덕스 툴킷을 이용해 기존 리덕스 문법보다 훨씬 쉬운 방법으로 상태 관리를 할 수 있는데, 리덕스 개발사 쪽에서 리덕스 툴킷을 쓰라고 하는 것 같다.
리액트 환경에서 아래의 카운트업 코드를 리덕스 툴킷을 통해 만들어 보자.
// App.jsx
import React, { useState } from 'react'
export default function App() {
const [counter, setCounter] = useState(0);
const minus = () => {
setCounter(prev => prev - 1);
}
const plus = () => {
setCounter(prev => prev + 1);
}
return (
<div>
<button onClick={minus}>-</button>
Value: { counter }
<button onClick={plus}>+</button>
</div>
);
}
리덕스 툴킷 역시 설치가 필요하다.
npm install @reduxjs/toolkit
리액트 환경이라면 react-redux
설치가 필요하다.
Provider
는 react-redux
에서 리액트 앱에 스토어를 연동할 수 있게 해주는 컴포넌트다. 아래와 같이 Provider
컴포넌트를 불러와 연동할 컴포넌트를 감싸준 뒤, Provider
의 props로 사용할 스토어를 지정해주면 된다.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { Provider } from "react-redux";
import store from "./store/store.js";
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
)
먼저 store.js에 configureStore()
로 스토어를 만들어주자.
// store.js
import { configureStore } from '@reduxjs/toolkit';
export const store = configureStore({
reducer: counterSlice,
middleware: [...middlewares]
})
기존 리덕스에서는 스토어 생성 후 미들웨어가 한 개 이상이라면, 여러 메서드를 통해 긴 코드를 작성해야 했다.
configureStore()
는 별도의 메서드 없이 바로 미들웨어를 추가할 수 있다는 장점이 있다.
기존 리덕스에서는 액션을 디스패치하기 위한 별도의 함수가 필요했고, 액션의 객체를 리듀서를 통해 리턴하는 구조였다.
createSlice()
는 액션에 대한 함수 설정과 리듀서를 따로 생성하지 않아도 된다.
아래는 createSlice()
를 사용한 카운트업 코드와 그에 대한 설명이다.
// countSlice.jsx
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
plus: state => {
state.value += 1
},
minus: state => {
state.value -= 1
},
},
})
export const { plus, minus } = counterSlice.actions;
export default counterSlice.reducer;
slice.reducer
로 내보낸다. store.js는 위 파일을 전부 리듀서로 받는다.useSelector()
는 기존 리덕스의 connect()
를 이용하지 않고 리덕스의 상태를 조회할 수 있다.
useDispatch()
는 생성한 액션을 발생시키며, 액션생성 함수를 가져온다. 위 설명의 디스패치와 같다고 보면 된다.
아래는 useSelector()
와 useDispatch()
에 대한 설명이다.
// App.jsx
import React from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { plus, minus } from './counter/countSlice01';
export default function App() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(minus())}>-</button>
Value: { count }
<button onClick={() => dispatch(plus())}>+</button>
</div>
);
}
useSelector()
로 스토어에서 현재 상태 값을 가져온다.useDispatch()
를 통해 변경되는 값을 스토어로 전달한다.끝.
리덕스민수야 고맙다🙏