리액트를 하면서 반드시 필요한 것이 전역 상태 관리이다. state를 전역에서 관리하지 않으면 지옥같은 props 연결을 보게 될 것이다. 그래서 반드시 공부해야 하는 것이 전역 상태 관리인데 Redux, MobX, Context API, Recoil 등 종류도 많다. 그만큼 리액트에서 상태관리가 힘드니까 그렇겠지 🤦🏻♀️
그 중에서도 Redux가 아마 사용자가 가장 많은 것으로 알고 있다. 내가 처음 접한것도 Redux였기도 하고 다른 상태관리 라이브러리에 비해 정보도 더 많을 것이다. 근데 MobX든 Recoil이든 내부 형태는 동일하다.
그것이 리액트니까.(끄덕)
그리고 Redux와 Redux-toolkit의 차이를 간단히 얘기하자면 Redux를 사용할 때는 Redux 외에도 Redux-actions 라던가 redux-thunk라던가 뭔가 많다. 뭘 자꾸 설치하라는데 쓰다보면 엄청 헷갈린다. 그래서 나온게 redux-toolkit인데 이거 쓰면 지저분하게 이것저것 설치할 필요가 없다는 말씀. 🤷🏻♀️
리덕스 툴킷을 이용해서 간단한 상태관리 기능을 배워볼 것이다. 이미 이런 글이 많은데 적는 이유가 무엇인고 하니 내가 찾아본 튜토리얼은 죄다 카운터앱밖에 없다... 난 카운터앱따위가 궁금한게 아닌데!! 물론 카운터앱으로도 어떻게 구성되어있는지는 알겠는데 그걸 실제 프로젝트에 써먹을려니 어떻게 활용해야할지 도무지 모르겠다. (내가 멍청한걸지도)
redux
react-redux
@reduxjs/toolkit
yarn 사용할거면 yarn add redux react-redux @reduxjs/toolkit
npm 사용할거면 npm install redux react-redux @reduxjs/toolkit
하나만 설치하라면서 왜 이렇게 설치할게 많냐고 물으면 할말이 없다. 이 정도는 버텨야한다. 그것이 리액트니까...
일단 앱부터 만들고 시작한다.
npx create-react-app redux-prac
그리고 App.js 들어가서 필요없는거 다 지운다.
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
사실 css도 필요없다. 맘에 안들면 지워도 됨
아주아주 간단한 로그인, 로그아웃 앱을 만들것이다.
src 폴더에 components 폴더 만들고 Login.js, Profile.js 만든다.
Profile.js
import React from 'react'
function Profile() {
return (
<div>
<h1>Profile Page</h1>
<p> Name : </p>
<p> Age : </p>
<p> Email : </p>
</div>
);
}
export default Profile
Login.js
import React from 'react'
function Login() {
return (
<div><button>Login</button></div>
);
}
export default Login
App.js
import './App.css';
import Profile from './components/Profile';
import Login from './components/Login';
function App() {
return (
<div className="App">
<Profile/>
<Login/>
</div>
);
}
export default App;
위 처럼 app.js에 연결시켜주면
이렇게 나온다.
이제 store.js 만들어 줄꺼다.
리덕스는 스토어에 모든 state 상태값을 저장한다. 리덕스 안쓰면 부모 컴포넌트에 있는 변수를 자식 컴포넌트가 못 건든다.
그래서 아예 스토어라는 다른 곳에 변수 죄다 때려박아 놓고 원격으로 들고오는 거임 ㅇㅇ
src에 redux폴더 만들고 store.js 만든다.
공식문서는 app폴더에 만들던데 어디다 만들던 상관없다. index.js에 만드는 사람도 있음 <- 이건 딱히 추천 안한다.
store.js
import React from 'react';
import { configureStore } from '@reduxjs/toolkit';
export default configureStore({
reducer: {},
})
configureStore라는 친구가 reducer를 감싸고 있다.
그렇다. 저기서 모든 state를 관리할거라는 뜻이다.
reducer가 비어있는데 저기다가 이제 상태관리할 것들 저장할거다.
이제 스토어를 index.js에 연결시켜 줄거다.
그리고 index에다가 Provider라는걸로 감싸줘야 한다.
Provider가 뭐냐면 store가 리액트앱 전체를 감싸도록 해주는 애다.
자세한건 알면 골치아픔
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './redux/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
위 코드를 보면 Provider라는 친구가 app.js를 감싸고 있다. 그리고 store라는 파라미터를 전달해준다.
저거 없으면 멍청한 리액트는 스토어가 뭔지 모르니 꼭 적도록.
이제 기본세팅 끝났다. 본격적을 사용해보러 긔긔!
이제 리듀서라는 것을 만들어보자. 스토어가 식당이라고 하면 리듀서는 접시 같은 놈이다.
식당에서 음식을 주는데 접시가 있어야지. 맨 손으로 받아먹을거 아니잖슴.
redux 폴더에 user.js 를 만든다.
user.js
import { createSlice } from '@reduxjs/toolkit';
export const userSlice = createSlice({
name: "user",
initialState: { value: {name: "", age: 0, email: ""}},
reducers: {
login: (state, action) => {
state.value = action.payload
},
},
});
export default userSlice.reducer;
createSlice 라는 친구가 굉장한 친구다. 기존에 createReducer와 createAction 이 하던 일을 같이 해준다. 쉽게 말해 actions를 위한 js파일을 따로 만들 필요가 없음!
사용할 리듀서의 이름을 정하고 그걸 createSlice로 지정해준다.
name은 리듀서 이름 뭘로 할지 정하는거고
initialState는 들어갈 데이터의 초기값 잡아주는 용도.
reducers에서 이제 상태가 변하면 어떻게 실행될지 정하는 부분이다.
우리는 로그인 버튼을 눌렀을때 이름, 나이, 이메일이 변하게 하고 싶으니
로그인 함수를 만들어준다. state는 우리가 잡아놓은 초기값의 value를 가져오는 역할을 하고
actions안에 payload랑 type 이라는 친구가 있는데 우리가 바꾸고 싶은 데이터를 원하는 곳에다가 넘겨주는 역할을 한다.
이제 리듀서 만들었으니까 스토어에 저장해야 된다. 안그럼 작동 안함요
store.js
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './user'
export default configureStore({
reducer:{
user: userReducer
}
})
그럼 이제 Profile.js에 가서 실행되는지 보자.
먼저 useSelector라는 것을 import 해준다.
import { useSelector } from 'react-redux'
그리고 밑에 코드 작성한다.
const user = useSelector((state) => state.user.value)
하나하나 뜯어보자면 useSelector를 이용하면 우리가 만든 리듀서에 접근할 수 있다.
리듀서에 있는 state에 접근할건데, 어느 state냐 하면 아까 user라고 이름 지어준 리듀서에 있는 state에 접근할거란 소리
그리고 user 리듀서에 우리가 초기값을 어떻게 설정했는지 보면 value라고 해놨다. 그래서 user에 있는 value 가져올거란 뜻이다.
이제 value있던 name, age, email을 붙여준다.
Profile.js
import React from 'react'
import { useSelector } from 'react-redux'
function Profile() {
const user = useSelector((state) => state.user.value)
return (
<div>
<h1>Profile Page</h1>
<p> Name : {user.name} </p>
<p> Age : {user.age} </p>
<p> Email : {user.email} </p>
</div>
);
}
export default Profile
그리고 확인해보면?
Age에 0이 생겼다.
초기값에 string을 빈값을 넣어놨으니 0만 나오는게 맞다.
이제 그럼 로그인 버튼을 눌렀을때 값이 들어가도록 만들어 보자.
user.js로 가서
export const { login } = userSlice.actions;
이거 한줄 붙여준다.
아까 만들어둔 login이라는 함수를 action기능이 작동하도록 다른데서 쓸거라는 뜻이다.
export default 어쩌구 되있는 마지막줄 위에다 붙이셈
그리고 login.js로 가서 useDispatch 라는 훅을 사용할 것이다.
import { useDispatch } from 'react-redux'
일케 적어준다.
그리고 Login 함수 안에 밑에 코드 작성한다.
const dispatch = useDispatch()
이거 쓰면 action을 보낼 수 있음
그리고 아까 만든 user 리듀서 들고오면 된다.
import { login } from '../redux/user';
그럼 이제 뭐 해야됨?
로그인 버튼을 누르면 업데이트 되도록 하면 된다.
Login.js 전체 코드
import React from 'react';
import { useDispatch } from 'react-redux';
import { login } from '../redux/user';
function Login() {
const dispatch = useDispatch()
return (
<div>
<button onClick={() => {
dispatch(login({name: "내 이름", age: 20, email: "email@gmail.com"}))
}}>Login</button>
</div>
);
}
export default Login
이제 버튼을 누르면?
뿅!
데이터가 잘 들어간 것을 확인할 수 있다.
그럼 이제 지우는 버튼도 만들어보자.
로그인 버튼 밑에 로그아웃 버튼 하나 만들어준다. 이건 코드 안적어줄거임(간단하니까)
로그아웃 기능을 만들기 위해 user.js로 간다.
로그인 함수 밑에 로그아웃 함수를 적어준다.
그런데 여기서 한가지 추가로 알려줄게 있는디 코드를 좀더 이쁘게 작성하는 법이다.
우리가 처음에 작성한 user.js 파일은 이렇다.
import { createSlice } from '@reduxjs/toolkit';
export const userSlice = createSlice({
name: "user",
initialState: { value: {name: "", age: 0, email: ""}},
reducers: {
login: (state, action) => {
state.value = action.payload
},
},
});
export const { login } = userSlice.actions;
export default userSlice.reducer;
근데 initialState는 리듀서를 쓰다보면 자주 건들게 되는데, 그럴때마다 { value: {name: "", age: 0, email: ""}}를 적어줘야 되는 귀찮은 일이 발생할 수 있다.
그래서 따로 빼주는 작업을 할거다.
import { createSlice } from '@reduxjs/toolkit';
const initialStateValue = {name: "", age: 0, email: ""}
export const userSlice = createSlice({
name: "user",
initialState: { value: initialStateValue},
reducers: {
login: (state, action) => {
state.value = action.payload
},
logout: (state) => {
state.value = initialStateValue
}
},
});
export const { login, logout } = userSlice.actions;
export default userSlice.reducer;
적는 김에 로그아웃 함수도 함께 적었다. 기존에 바꿔놓은 value값을 초기상태로 돌려야 하기때문에 initialState를 상수로 따로 빼놓고 로그아웃 함수에서 재활용하면 편하다.
그리고 다시 Login.js를 수정하러 가보자.
간단하다. 그냥 똑같이 적으면 된다.
Login.js
import React from 'react';
import { useDispatch } from 'react-redux';
import { login, logout } from '../redux/user';
function Login() {
const dispatch = useDispatch()
return (
<div>
<button onClick={() => {
dispatch(login({name: "내 이름", age: 20, email: "email@gmail.com"}))
}}>Login</button>
<button onClick={() => {
dispatch(logout())
}}>Logout</button>
</div>
);
}
export default Login
로그아웃 버튼에 dispatch(logout())를 추가해주면 된다. logout함수 자체가 이미 상태를 초기값으로 바꾸는 역할이니 안에 따로 뭘 적지 않아도 작동한다.
이걸로 간단한 로그인 로그아웃 상태관리가 완성되었다! 이걸로 끝내도 되지만 한가지만 더 해보자
store에 reducer를 여러개 넣고 관리하는것도 해보고 싶지 않은가? 그렇지 않아도 한번 해보자. 🙆🏻♀️
우리가 만든 앱의 글 색깔을 바꾸는 기능을 추가해보자. 싫어도 선택권은 없다.
component 폴더에 ChangeColor.js 파일을 만든다.
ChangeColor.js
import React, {useState} from 'react'
function ChangeColor() {
const [color, setColor] = useState('');
return (
<div>
<input
type="text"
onChange={e => {
setColor(e.target.value);
}}
/>
<button>CHANGE COLOR</button>
</div>
)
}
export default ChangeColor
input을 이용해서 색을 바꿔줄거다.
redux 폴더에 theme.js라는 파일을 만들도록 하자.
이번엔 전에 만든 user.js의 전체를 복붙한다. 이미 만들었는데 처음부터 하는건 세상 비효율적인 일이다.
그리고 reducers안을 비워주고 이름은 themeReducer로 바꾸자
theme.js
import { createSlice } from '@reduxjs/toolkit'
const initialStateValue = "";
export const themeSlice = createSlice({
name: "theme",
initialState: { value: initialStateValue },
reducers: {
changeColor: ( state, action ) => {
state.value = action.payload
},
},
});
export const { changeColor } = themeSlice.actions;
export default themeSlice.reducer;
요런 모양새가 된다. 이번엔 string을 넘길것이기 때문에 초기값은 배열이 아닌 빈값으로 지정한다.
이제 새로만든 themeReducer를 스토어에 연결시켜 보자.
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './user'
import themeReducer from './theme'
export default configureStore({
reducer:{
user: userReducer,
theme: themeReducer
}
})
이름 적고 themeReducer 가져오면 끝!
이제 다시 profile.js로 간다.
글자의 색이 바뀌게 하고 싶으니 div에 스타일을 하나 줘서 인풋의 값이 css 요소로 들어가게 할것이다.
Profile.js
import React from 'react'
import { useSelector } from 'react-redux'
function Profile() {
const user = useSelector((state) => state.user.value);
const themeColor = useSelector((state) => state.theme.value);
return (
<div style={{ color: themeColor }}>
<h1>Profile Page</h1>
<p> Name : {user.name}</p>
<p> Age : {user.age}</p>
<p> Email : {user.email}</p>
</div>
);
}
export default Profile
useSelector로 theme리듀서를 가져온다. 변수명은 맘대로 지정하면 됨.
그리고 div에 style을 먹여서 color:themeColor로 넣어주면
세상에!
색깔이 바뀌었다. 인풋에 css에서 사용하는 색상명을 아무거나 적으면 그걸로 바뀐다.
물론 이상한거 적으면 오류가 나지만 여기까지 한게 어디임!
이렇게 Redux-toolkit을 이용해서 상태관리를 하는 방법과 multiple-reducer를 어떻게 이용하는지 까지 알아봤다. 이거 적는데 하루종일 걸린 것 같다. 역시 블로그는 귀찮아...
나도 리덕스가 익숙하지 않고 나처럼 어떻게 쓰는지 모르는 사람이 있을 것 같아 글을 작성하게 되었다.
두고두고 까먹을 때마다 봐야지
[Github Link] : https://github.com/mael1657/redux-toolkit-tutorial
예제 내용을 정리한 깃허브를 추가했다! 많은 사람들에게 도움이 되면 좋겠다 🤗
너무 잘 정리되어있어서, 큰 도움이 되었습니다. 감사합니다 ✨