let's get IT with DAVINA ๐ป
Redux Toolkit ๋ณธ๋ฌธ
Redux
- React์ ๋ฌด๊ดํ ์ํ๊ด๋ฆฌ์
React Redux
- React & Redux ํตํฉ
Redux Toolkit
- Redux๋ฅผ ๋ณด๋ค ์์ํ๊ฒ ์ด์ฉํ๊ฒ ํด์ฃผ๋ ๋๊ตฌ
Redux Toolkit ๋๋ ์ ํ์ด๋ฌ๋?
- ์ค์ ํ ๊ฒ ๋๋ฌด ๋ง์ต๋๋ค.
- ๋ฏธ๋ค์จ์ด ์ค์น๊ฐ ๋ณต์กํฉ๋๋ค.
- ๋ฐ๋ณต๋๋ ์ฝ๋๊ฐ ๋๋ฌด ๋ง์ต๋๋ค.
- ์์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋ถ๋ณ์ฑ ์ ์ง๊ฐ ๋๋ฌด ์ด๋ ต์ต๋๋ค.
Redux Toolkit ์ฌ์ฉ๋ฒ
npm install @reduxjs/toolkit
npm install react-redux
์์ store์ด ๋ชจ์ด๋ฉด? → slice
slice๋ค์ด ๋ชจ์ด๋ฉด? → ํฐ store
- createStore๋ฅผ ๊ฐ์ธ์ ์ธ๋งํ ๊ธฐ๋ณธ๊ฐ๋ค๊ณผ ๋จ์ํ๋ ์ค์ ์ ์ ๊ณตํฉ๋๋ค.
- ๋ด๊ฐ ๋ง๋ ๋ฆฌ๋์ ์กฐ๊ฐ๋ค์ ์๋์ผ๋ก ํฉ์ณ์ฃผ๊ณ , ๊ธฐ๋ณธ ์ ๊ณต๋๋ redux-thunk๋ฅผ ํฌํจํด์ ๋ด๊ฐ ์ง์ ํ ๋ฏธ๋ค์จ์ด๋ค์ ๋ํด์ฃผ๊ณ , Redux DevTools ํ์ฅ(๊ตฌ๊ธ ํ์ฅ ํ๋ก๊ทธ๋จ)์ ์ฌ์ฉํ ์ ์๊ฒ ํฉ๋๋ค.
- switch ๋ฌธ์ ์์ฑํ๋ ๋์ , ์ก์ ํ์ ๊ณผ ๋ฆฌ๋์ ํจ์๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ๋ชฉ๋ก์ ์์ฑํ๋๋ก ํฉ๋๋ค.
- ์ฌ๊ธฐ์ ๋ํด immer ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์ฌ์ฉํด์, state.todos[3].completed = true์ ๊ฐ์ ๋ณ์ด ์ฝ๋๋ฅผ ํตํด ๊ฐํธํ๊ฒ ๋ถ๋ณ ์ ๋ฐ์ดํธ๋ฅผ ํ ์ ์๋๋ก ํฉ๋๋ค. (๋ถ๋ณ์ฑ ์ ์ง๋ฅผ ์ํด๋ ๋๋ ์ด์ !)
- ์ฃผ์ด์ง ์ก์ ํ์ ๋ฌธ์์ด์ ์ด์ฉํด ์ก์ ์์ฐ์ ํจ์๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
- ํจ์ ์์ฒด์ toString() ์ ์๊ฐ ํฌํจ๋์ด ์์ด์, ํ์
์์๊ฐ ํ์ํ ๊ณณ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๋ง๋ค์ด์ง action ํจ์ ์์๋ type๊ณผ payload๊ฐ ๋ด๊ฒจ ์๋ค.
- ex) createAction("ADD") → {type: 'ADD', payload: undefined} type์ ADD๊ฐ ๋๊ณ , dispatch๋ก ๋ฐ์์จ ๊ฐ๋ค์ด payload์ ๋ด๊ธฐ๊ฒ ๋๋ค.
- ์ด ๋, 2๊ฐ ์ด์์ payload๋ฅผ ํ ๋นํ๋ ๊ฒฝ์ฐ? → 1๊ฐ์ ๊ฒฝ์ฐ dispatch(TodoAdd(text)) ์์ฑ / 2๊ฐ์ ๊ฒฝ์ฐ dispatch(TodoAdd({text,id}) ์ ๋ฌ์ธ์์ ๊ฐ์ฒด๋ก ๋ฌถ์ด ์ ๋ฌํ๋ฉด ๊ฐ์ฒด์ฒ๋ผ ์ฌ์ฉ ๊ฐ๋ฅ text: action.payload.text, id:action.payload.id
- createSlice๋ผ๋ ๊ธฐ๋ฅ ๋์ ์ฌ์ฉ ์ถ์ฒ!! ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ฉด createAction์ ํตํด ๋ฐ๋ก ์ก์ ํ์ ์ ์ ์ํ์ง ์์๋ ์๋์ผ๋ก ์ก์ ํ์ ์ ๋ง๋ค์ด์ค๋๋ค.
const increment = createAction("INCREMENT");
const decrement = createAction("DECREMENT");
function counter(state = 0, action) {
switch (action.type) {
case increment.type:
return state + 1;
case decrement.type:
return state - 1;
default:
return state;
}
}
const store = configureStore({
reducer: counter
});
document.getElementById("increment").addEventListener("click", () => {
store.dispatch(increment());
});
- ์กฐ๊ฐ ์ด๋ฆ๊ณผ ์ํ ์ด๊ธฐ๊ฐ, ๋ฆฌ๋์ ํจ์๋ค๋ก ์ด๋ฃจ์ด์ง ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๊ทธ์ ๋ง๋ ์ก์ ์์ฐ์์ ์ก์ ํ์ ์ ํฌํจํ๋ ๋ฆฌ๋์ ์กฐ๊ฐ์ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋๋ค.
- ์ก์ ์ ๋ํ ํจ์ ์ค์ ๊ณผ ๋ฆฌ๋์๋ฅผ ๋ฐ๋ก ์์ฑ ์ํด๋ ๋จ!
- ์ก์ ํ์ ๋ฌธ์์ด๊ณผ ํ๋ก๋ฏธ์ค๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ๋ฐ์, pending/fulfilled/rejected ์ก์ ํ์ ์ ๋์คํจ์นํด์ฃผ๋ thunk๋ฅผ ์์ฑํด์ค๋๋ค.
- ์ ์ฅ์ ๋ด์ ์ ๊ทํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํ ๋ฆฌ๋์์ ์ ๋ ํฐ๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
import {createSlice, configureStore} from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'in',
initialState: {value:0},
reducers:{
up:(state,action)=>{
//state.value = state.value + action.step;
state.value = state.value + action.payload; //counterSlice.actions๋ฅผ ์ธ๋
}
}
});
//store ๋ง๋ค๊ธฐ
const store = configureStore({
reducer: { //๊ฐ๊ฐ์ ์ฌ๋ผ์ด์ค์ ๋ฆฌ๋์
counter: counterSlice.reducer //counterSlice์์ reducers๋ค์ ํ๋๋ก ํฉ์ณ์ฃผ๋ ํ๋์ reducer ์์ฑ
}
});
function Counter(){
const dispatch = useDispatch();
const count = useSelector(state=>{
return state.counter.value
});
return <div>
<button onClick={()=>
//dispatch({type:'counterSlice/up', step:2});
dispatch(counterSlice.actions.up(2)) //counterSlice.actions๋ฅผ ์ธ๋
}}>+</button> {count}
</div>
}
export default function App() {
return (
<Provider store={store}>
<div>
<Counter></Counter>
</div>
</Provider>
)
}
๋ ๊ฐ ์ด์์ reducer๋ฅผ ์ธ ๋
์์ ๐ฝ
- src/store.js
import { configureStore } from "@reduxjs/toolkit";
import LoginState from "./LoginSlice";
import ModalState from "./ModalSlice";
export const store = configureStore({
reducer: {
Login: LoginState.reducer, //key:value๊ฐ ์๋ ๊ฐ์ฒด ํ์์ด์ด์ผํจ!
Modal: ModalState.reducer, //์ด๋ value๋ reducer๊ฐ ๋ค์ด์ด!
},
});
export default store;
- src/ModalSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isModal: false,
};
const ModalState = createSlice({
name: "modalstate",
initialState,
reducers: {
modalOpen: (state) => {
state.isModal = true;
},
modalClose: (state) => {
state.isModal = false;
},
},
});
export const { modalClose, modalOpen } = ModalState.actions; //reducers์ ๋ฌ
export default ModalState;
- src/LoginSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
isLogin: false,
};
const LoginState = createSlice({
name: "loginstate",
initialState,
reducers: {
login: (state) => {
state.isLogin = true;
},
logout: (state) => {
state.isLogin = false;
},
},
});
export const { login, logout } = LoginState.actions; //reducers์ ๋ฌ
export default LoginState;
useDispatch & useSelector ์ ์ฉ๋ฒ
useSelector()
- ๋ฆฌ๋์ค์ ์ํ๋ฅผ ์กฐํํ ์ ์์ต๋๋ค.
- store์์ ํ์ฌ ์ํ ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
useDispatch()
- ์์ฑํ ์ก์ ์ ๋ฐ์์ํค๋ฉฐ, ์ก์ ์์ฑ ํจ์๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- ๋ณ๊ฒฝ๋๋ ๊ฐ์ store๋ก ์ ๋ฌํฉ๋๋ค.
- useDispatch()
export default function LoginModal({ isModal }) {
const dispatch = useDispatch();
const onValid = async (data) => {
try {
await axios
.post("http://localhost:3001/login", data)
.then((data) => {
closeModal();
dispatch(login()); //dispatch ์ ์ฉ! (์ ๋ฌ์ธ์๋ ์ํ ๋ณํ๋ฅผ ์ฃผ๋ actionํจ์)
Toast.fire({
title: "๋ก๊ทธ์ธ ์ฑ๊ณต!",
icon: "success",
customClass: {
icon: "icon-class",
container: "my-swal",
},
});
});
} catch (error) {
console.error(error);
}
};
- useSelector()
const isLogin = useSelector((state) => state.Login.isLogin);
const isModal = useSelector((state) => state.Modal.isModal);
<ConfirmButton onClick={
isLogin? handleConfirm : () => dispatch(modalOpen())}
isModal={isModal}
>
์์ฝํ๊ธฐ
</ConfirmButton>
Comments