let's get IT with DAVINA ๐Ÿ’ป

Redux Toolkit ๋ณธ๋ฌธ

DEV_IN/Library

Redux Toolkit

๋‹ค๋นˆ์น˜์ฝ”๋“œ๐Ÿ’Ž 2023. 2. 21. 18:41
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

 configureStore()

  • createStore๋ฅผ ๊ฐ์‹ธ์„œ ์“ธ๋งŒํ•œ ๊ธฐ๋ณธ๊ฐ’๋“ค๊ณผ ๋‹จ์ˆœํ™”๋œ ์„ค์ •์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋‚ด๊ฐ€ ๋งŒ๋“  ๋ฆฌ๋“€์„œ ์กฐ๊ฐ๋“ค์„ ์ž๋™์œผ๋กœ ํ•ฉ์ณ์ฃผ๊ณ , ๊ธฐ๋ณธ ์ œ๊ณต๋˜๋Š” redux-thunk๋ฅผ ํฌํ•จํ•ด์„œ ๋‚ด๊ฐ€ ์ง€์ •ํ•œ ๋ฏธ๋“ค์›จ์–ด๋“ค์„ ๋”ํ•ด์ฃผ๊ณ , Redux DevTools ํ™•์žฅ(๊ตฌ๊ธ€ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ)์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

createReducer()

  • switch ๋ฌธ์„ ์ž‘์„ฑํ•˜๋Š” ๋Œ€์‹ , ์•ก์…˜ ํƒ€์ž…๊ณผ ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ๋ชฉ๋ก์„ ์ž‘์„ฑํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ์—ฌ๊ธฐ์— ๋”ํ•ด immer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•ด์„œ, state.todos[3].completed = true์™€ ๊ฐ™์€ ๋ณ€์ด ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ๋ถˆ๋ณ€ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. (๋ถˆ๋ณ€์„ฑ ์œ ์ง€๋ฅผ ์•ˆํ•ด๋„ ๋˜๋Š” ์ด์œ !)

createAction()

  • ์ฃผ์–ด์ง„ ์•ก์…˜ ํƒ€์ž… ๋ฌธ์ž์—ด์„ ์ด์šฉํ•ด ์•ก์…˜ ์ƒ์‚ฐ์ž ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
  • ํ•จ์ˆ˜ ์ž์ฒด์— 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());
});

createSlice()

  • ์กฐ๊ฐ ์ด๋ฆ„๊ณผ ์ƒํƒœ ์ดˆ๊ธฐ๊ฐ’, ๋ฆฌ๋“€์„œ ํ•จ์ˆ˜๋“ค๋กœ ์ด๋ฃจ์–ด์ง„ ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ๊ทธ์— ๋งž๋Š” ์•ก์…˜ ์ƒ์‚ฐ์ž์™€ ์•ก์…˜ ํƒ€์ž…์„ ํฌํ•จํ•˜๋Š” ๋ฆฌ๋“€์„œ ์กฐ๊ฐ์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
  • ์•ก์…˜์— ๋Œ€ํ•œ ํ•จ์ˆ˜ ์„ค์ •๊ณผ ๋ฆฌ๋“€์„œ๋ฅผ ๋”ฐ๋กœ ์ƒ์„ฑ ์•ˆํ•ด๋„ ๋จ!

createAsyncThunk

  • ์•ก์…˜ ํƒ€์ž… ๋ฌธ์ž์—ด๊ณผ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ›์•„, pending/fulfilled/rejected ์•ก์…˜ ํƒ€์ž…์„ ๋””์ŠคํŒจ์น˜ํ•ด์ฃผ๋Š” thunk๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

createEntityAdapter

  • ์ €์žฅ์†Œ ๋‚ด์— ์ •๊ทœํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ๋ฆฌ๋“€์„œ์™€ ์…€๋ ‰ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
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>

'DEV_IN > Library' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Redux  (4) 2023.02.09
Comments