리덕스는 자바스크립트 앱을 위한 상태 관리 라이브러리 입니다.
그동안의 컴포넌트 간 데이터 전달은, 자식과 부모 컴포넌트 끼리만 일어나는 것이었습니다. 만약 앱이 커지게 된다면, 향후 데이터를 관리하는데 매우 복잡해질 것입니다. 부모에서 자식으로, 자식에서 또 그 자식으로, 또 그 자식에서 그 자식의 자식으로 이어지는 작업을 해줘야겠죠.
이를
props drilling
이라고 합니다.
이를 위해 나타난 것이 context API
와 같은 전역 데이터 공유 라이브러리입니다. 이를 통해 비효율적인 props
작업을 피할 수 있게 되었죠.
물론 이전에 Context API
를 통해 데이터 공유를 하는 방법을 이미 배웠습니다. 물론 둘다 상태관리에 초점이 맞춰져있기 때문에 Redux
에서 할 수 있는 일을 Context API
를 통해 가능도 하구요.
실제로는 Context API
는 상태관리를 하진 않습니다. 단지 종속성 주입의 형태일 뿐 상태관리는 useState
와 useReducer
를 함께 사용해줘야 합니다. 굳이 표현하자면, Context API
는 props drilling
을 회피하기 위한 용도로 사용되는 것이라고 보는게 맞는 거 같습니다.
또한 데이터를 보내기 위해서는 Context API
파일 마다 Provider
를 설정해줘야하는데 데이터가 커질수록 점점 코드를 짜는데 복잡해질 것입니다. 결국 이후에는 여러가지를 중첩한 JSX 코드가 짜지기도 하겠네요.
...
...
return (
<AuthContextProvider>
<ThemeContextProvider>
<UIInteractionContextProvider>
<MultiStepFormContextProvider>
<UserRegistration/>
</MultiStepFormContextProvider>
</UIInteractionContextProvider>
</ThemeContextProvider>
</AuthContextProvider>
)
Context API
는 리액트에서만 쓸 수 있는데 비해 리액트 외의 다른 프레임워크 환경에서도 쓰는 게 가능합니다.
성능적인 면에서도 훨씬 강하다는 이야기가 있습니다. Context API
는 불필요한 props
를 작업을 줄이는 데에는 분명 도움이 되지만, 빈번하게 데이터가 변화하는 작업에는 좋지 않다고 합니다. 데이터 보안 및 변질성 측면에서는 Redux
가 더욱 큰 힘을 발휘한다고 합니다.
그 외에도 관련 커뮤니티가 매우 큽니다. Context API
가 등장하기 이전 부터 이미 많이 쓰였던 라이브러리 였기에, 원하는 Redux 관련 자료를 빠르게 찾는게 가능합니다.
또한 Context API
와 다르게 하나의 js 파일을 기준으로 다양한 상태 객체를 모듈화 하는 것이 가능하며, Provider
설정 역시 합번에 간편하게 할 수 있습니다.
외에도 단방향성에 의한 예측이 가능한 상태관리 컨테이너를 설정할 수 있다는 것과, thunk
나 saga
와 같은 미들웨어를 추가적으로 구성할 수 있다는 것과, 익스텐션을 통해 디버깅이 간편하다는 등의 강점을 가지고 있습니다.
// react-redux && @reduxjs/toolkit
// index.js
// Provider 설정
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import store from "./store/index";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
//store/auth.js
// 컨테이너 모듈화1
import { createSlice } from "@reduxjs/toolkit";
const initialAuthState = {
isAuthenticated: false,
};
const authSlice = createSlice({
name: "authentication",
initialState: initialAuthState,
reducers: {
login(state) {
state.isAuthenticated = true;
},
logout(state) {
state.isAuthenticated = false;
},
},
});
export const authActions = authSlice.actions;
export default authSlice.reducer;
// store/counter.js
// 컨테이너 모듈화2
import { createSlice } from "@reduxjs/toolkit";
const initialCounterState = { counter: 0, showCounter: true };
const counterSlice = createSlice({
name: "counter",
initialState: initialCounterState,
reducers: {
increment(state) {
state.counter++;
},
decrement(state) {
state.counter--;
},
increase(state, action) {
state.counter += action.payload;
},
toggleCounter(state) {
state.showCounter = !state.showCounter;
},
},
});
export const counterActions = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
// 모듈화된 상태 컨테이너들 통합
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter";
import authReducer from "./auth";
const store = configureStore({
reducer: {
counter: counterReducer,
auth: authReducer,
},
});
export default store;