1학기 졸업작품 중간발표가 끝나고 여유가 생겨 평소 공부해 보고 싶었던 상태 관리 라이브러리 Redux를 공식문서를 참고해 정리해 보겠습니다. 예전에 사용해 봤을 때는 그냥 Redux를 사용했었는데 공식 문서를 확인해 보니 Redux 측에서 공식적으로 추천하는 Redux Toolkit 방법이 있길래 타입스크립트 없이 react에 적용하는 방법을 정리해 보겠습니다!
- react 프로젝트
- VS Code
- 라이브러리 설치
redux와 react-redux를 설치해 줍니다.
(react 프로젝트에 적용할 경우 react-redux를 추가로 설치해줘야 합니다.)
npm install redux react-redux
redux Toolkit 적용을 위해서는 추가적으로 설치가 필요합니다.
npm install @reduxjs/toolkit
하위 컴포넌트에서 상위 컴포넌트의 값을 사용해야 하는 경우가 생겼을 때 props를 이용해서 값을 넘겨줄 수 있는데 치명적인 문제점이 하나 존재합니다.
위 사진의 props 그림처럼 상위 컴포넌트(A)에서 하위 컴포넌(B)로 값 전달은 가능하지만 A에서 C로 한 번에 값 전달은 불가능합니다. 예시로 든 자료는 A에서 C 컴포넌트의 거리가 가까워 A -> B -> C 루트가 가능하지만 만약 (알파벳 순서대로 컴포넌트 이름을 지었다고 가정) R컴포넌트까지 값을 전달하려면 A -> B -> C -> D -> ... -> R과 같이 props를 계속 전달하면 가능하지만 상당히 비효율적인 방법이 됩니다. 그래서 등장한 것이 바로 상태 관리 라이브러리입니다.
대표적인 상태 관리 라이브러리
Redux를 사용하면 그림과 같이 Store라는 공간이 존재하고 각 컴포넌트의 위치를 따지지 않고 값을 공유해서 사용이 가능하게 됩니다.
공식문서 피셜로는
"기존 Redux 문서에서 보여준 패턴들은 아주 장황하고 반복적인 코드를 필요로 합니다. 이러한 많은 보일러 플레이트 코드들은 Redux를 사용하는 데 필요하지 않습니다 게다가, 이러한 보일러 플레이트 코드는 더 많은 실수를 유발할 가능성이 있습니다."
요약하면 불필요한 코드를 줄여 실수를 방지하고 효율성을 올리겠다는 의도인 것 같습니다.
간단한 기능 테스트를 위해 count를 +, - 하는 예제를 만들어 보겠습니다.
우선 상태를 관리하는 공간 스토어를 만들어 보겠습니다. (저는 redux 폴더 안에 store.js를 만들었습니다.)
const store = createStore(reducers, applyMiddleware(thunk)); // 기존 코드
기존에 redux에서 스토어를 생성할 경우 미들웨어가 한 개 이상이라면 applyMiddleware를 통해 미들웨어를 합치거나 개발 확장 프로그램, 리듀서, 미들웨어를 추가할 경우 코드가 복잡하게 구성되는 상황이 많이 발생했었는데 configureStore를 통해 리듀서와 미들웨어를 간편하게 추가할 수 있게 되었습니다.
count 예제를 위해 count 리듀서를 만들어보겠습니다.
redux > store.js
import { configureStore } from '@reduxjs/toolkit';
import countSlice from './countSlice';
export const store = configureStore({
reducer: {
count: countSlice // counterSlice는 뒤에 등장하는 파일입니다! 일단 넘겨주세요
}
})
다음으로 만들어준 스토어를 연동해 주기 위해 index.js로 이동해 줍시다.
react-redux에 Provider 컴포넌트를 호출해 App을 감싸준 뒤에 props로 스토어를 적어줍니다.
(Provider 컴포넌트는 리액트 앱에 스토어를 연동시켜주는 역할을 수행)
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { store } from '../src/redux/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
각 리듀서에 대한 액션 생성자 함수를 자동으로 생성하고, 리듀서 이름에 기반해 내부적으로 액션 타입 문자열을 생성하는 역할을 수행합니다. (타입스크립트와 호환성이 좋다고 합니다.)
createSlice 덕분에 기존 리덕스에서 action value, action creator, initial state, reducer를 개별적으로 작성하던 불편함을 해결할 수 있게 되었습니다.
countSlice.js를 만들어주고 name은 count, 초기값은 0으로 설정하고 리듀스에 각각 +1과 -1을 수행하는 plus와 minus 액션을 설정해 주겠습니다.
import { createSlice } from '@reduxjs/toolkit'
export const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
plus: state => {
state.value += 1
},
minus: state => {
state.value -= 1
},
},
})
export const { plus, minus } = countSlice.actions;
export default countSlice.reducer;
countSlice.reducer로 export 하는 이유는 스토어는 값을 전부 리듀서로 받기 때문에 리듀서의 값을 내보내 줍니다.
export default countSlice.reducer;
이제 App.js로 돌아와 useSelector를 통해 스토어에서 변경된 값을 받아와 count에 저장하고 dispatch를 통해 plus, minus로 count 값을 변경해 주면 끝입니다.
import React from 'react'
import { useDispatch, useSelector } from 'react-redux';
import { plus, minus } from '../../redux/countSlice';
export default function App() {
const count = useSelector(state => state.count.value);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch(plus())}>+</button>
count: {count}
<button onClick={() => dispatch(minus())}>-</button>
</div>
);
}
useSelector
useDispatch
옛날에 힘들게 구현한 뒤에 사용했던 기억이 있어 다시 redux를 다시 사용하는 것에 대한 걱정이 많았었는데 코드도 간결해지고 더 쉬워진 것 같다. 다음번에는 현재 제작 중인 프로젝트에 적용하고 타입스크립트로 적용하는 방법을 찾아봐야겠다.