1) 리덕스 툴킷 공식문서: https://ko.redux.js.org/redux-toolkit/overview/
2) 생활코딩 유튜브: https://www.youtube.com/watch?v=9wrHxqI6zuM
3) Pedro Tech 유튜브: https://www.youtube.com/watch?v=Yokjzp91A4o
효율적인 Redux 개발을 위한 저희의 견해를 반영한,
"이것만으로도 작동하는 도구" 모음으로,
기존에 사용하기 어렵던 리덕스를 '리덕스 툴킷 개발진'의 주관적인 의견으로 사용이 쉽도록 만든 패키지이며,
store 만들기, 리듀서 정의, 불변 업데이트 로직, 액션 생산자나 액션 타입을 직접 작성하지 않고도 전역 상태인 슬라이스(Slice)를 만들어내는 기능까지 대부분의 Redux 사용 방법에 해당하는 유틸리티 함수를 포함한다.
그리고 비동기 로직을 위한 Thunk등의 애드온도 함께 포함되어 있다.
(출처: https://ko.redux.js.org/redux-toolkit/overview/)
즉, 리덕스의 자유도를 어느정도 저해하지만 보일러 플레이트를 줄이고
정말 자주 사용하는 기능들을 래핑해둔 것이다.
이것 또한 공식문서의 친절한 설명을 참고해서 말하자면
아래 3가지를 해결하기 위해서이다.
(1) "Store를 설정하는 것이 너무 복잡하다"
(2) "쓸만하게 되려면 너무 많은 패키지들을 더 설치해야 한다"
(3) "보일러플레이트 코드를 너무 많이 필요로 한다"
리덕스를 도입하는 많은 개발자들의 고민을 줄여줄만한 좋은 패키지라고 생각한다.
# NPM
npm i @reduxjs/toolkit react-redux
store.ts
를 만든다.import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
//여기에 추후에 리듀서를 넣어둔다.
},
});
방금 만든 redux디렉토리 안에 slices디렉토리를 만든다.
slice란, 리코일의 atom 같이 리덕스 툴킷이 관리하는 "전역 상태의 단위'이다.
리덕스 툴킷의 createSlice()
와 PayloadAction
타입을 사용하게 되는데,
createSlice()
란 말 그대로 slice를 쉽게 만들게 해주며 상태 이름, 초기 값, 리듀서들을 인자로 받는다.
아래는 createSlice()
의 선언문이다.
(줄이 길어서 조금 나눴는데 가독이 더럽다, 양해해주기 바란다..)
export declare function createSlice
<State, CaseReducers extends SliceCaseReducers<State>,
Name extends string = string>
(options: CreateSliceOptions<State, CaseReducers, Name>)
: Slice<State, CaseReducers, Name>;
slice의 이름을 문자열로 받고, CaseReducers를 받고, 초깃값인 State를 받는다. 이 3가지를 정의해주자.
slice디렉토리에 auth-slice.ts
를 만들자.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
const initialState = {}; // 아직 초기상태 정의되지 않음!
export const auth = createSlice({
name: "auth", // slice 이름으로 쓸 문자열!
initialState, // 아직 정의되지 않음!
reducers: {
// 액션 여기에 추가 예정!!
},
});
생각보다 간단하다.
그리고 이전 리덕스 툴킷을 사용하지 않을 때에는
authState.ts
, authActions.ts
, rootReducer.ts
등으로 파일 쪼개져서 관리하던걸 생각하면 여기서부터 코드량이 많이 줄어들었다고 느낄 수 있다.
그러면 이제 간단하게 유저 상태를 정의하고 로그인, 로그아웃 기능만 구현하자.
원래는 비동기로 유저 정보(AuthState타입의)를 받아와야 하지만
간단하게 로그인 창에 유저 이름을 입력하면 그 유저로 변경된다고 하자.
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
type InitialState = {
value: AuthState;
};
// 유저 상태를 정의할 타입을 만든다.
type AuthState = {
isAuth: boolean; // 로그인이 되었는가?
username: string; // 유저 닉네임
uid: string; // id
isModerator: boolean; // 관리자 계정인가요?
};
// 로그인 되지 않은 상태
const initialState = {
value: {
isAuth: false,
username: "",
uid: "",
isModerator: false,
} as AuthState,
} as InitialState;
export const auth = createSlice({
name: "auth", // slice name
initialState, // initial state
reducers: {
// 이 아래에 auth slice상태를 관리하기 위한 액션들을 정의하자.
logOut: () => {
return initialState;
},
// 로그아웃을 하면 유저가 없는 initialState로 변경
logIn: (State, action: PayloadAction<string>) => {
// 여기서 State란 초기상태인데, 사용하지 않으니 회색으로 뜰 것이다.
// _(언더 바)로 해둬서 사용하지 않음을 표시할 수도 있다.
return {
value: {
isAuth: true, // 로그인 했으니까 true
username: action.payload,
uid: "uid",
isModerator: false,
},
};
},
// 로그인을 하면 받아온 유저 정보로 변경
},
});
export const { logIn, logOut, toggleModerator } = auth.actions;
// 정의한 액션들을 export
export default auth.reducer;
// authReducer를 export
auth.reducer
로 하는데authReducer
로 한다는 점이다.store.ts
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./slices/auth-slice";
import { useSelector, TypedUseSelectorHook } from "react-redux";
export const store = configureStore({
reducer: {
authReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
프로바이더는 상태관리에 정말 자주 사용되는 개념인데,
리액트 context를 사용한 개념으로 Next Auth때도 다뤘을거다(세션 프로바이더 등).
프로바이더를 최상위에 씌워놓고 그 아래 있는 자식 컴포넌트라면 언제든 프로바이더에 있는 내용을 꺼내쓸수 있다.
이건 리덕스 툴킷 내용은 아니고, react-redux에 포함된 내용으로 사실 예전과 동일하다.
그러므로 빠르게 진행해보자.
redux폴더에 provider.tsx
를 만든다.
상태관리 관련된 컴포넌트는 'use client'는 필수!
"use client";
import { store } from "./store";
import { Provider } from "react-redux";
type Props = {
children: React.ReactNode;
};
export default function ReduxProvider({ children }: Props) {
return <Provider store={store}>{children}</Provider>;
}
src/app/layout.tsx
에 프로바이더를 씌워주자!layout.tsx
import "./globals.css";
import { Nanum_Gothic } from "next/font/google";
import ReduxProvider from "@/redux/provider";
const nanum_Gothic = Nanum_Gothic({ weight: "400", subsets: ["latin"] });
export const metadata = {
title: "Redux toolkit study",
description: "velog 좋아요",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={nanum_Gothic.className}>
<ReduxProvider>{children}</ReduxProvider>
// 바로 이곳!
</body>
</html>
);
}
이제 준비는 마쳤다. selector와 dispatch를 사용해서 본격적으로 상태관리를 해보자.
상태관리를 사용하는 컴포넌트는 항상 'use client'로 사용하는걸 잊지 말자
로그인 컴포넌트를 하나 만들자.
src/screens/LogInScreen.tsx
"use client";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { AppDispatch } from "@/redux/store";
import { logOut, logIn } from "@/redux/slices/auth-slice";
type Props = {
name: string;
};
export default function LogInScreen({ name }: Props) {
const [username, setUsername] = useState<string>("");
const dispatch = useDispatch<AppDispatch>();
const style = {
buttonStyle:
"px-4 py-2 m-4 text-white bg-blue-400 rounded-[1.1rem] ",
inputStyle: "m-4 border-2 border-blue-400 ",
};
const onClickLogIn = () => {
dispatch(logIn(username));
};
const onClickLogOut = () => {
dispatch(logOut());
};
return (
<div className="flex">
<input
className={style.inputStyle}
type="text"
onChange={(e) => setUsername(e.target.value)}
/>
<button className={style.buttonStyle} onClick={onClickLogIn}>
Log In
</button>
<button className={style.buttonStyle} onClick={onClickLogOut}>
Log Out
</button>
</div>
);
}
src/app.tsx
에 셀렉터와 로그인 스크린을 붙이자"use client";
import { useAppSelector } from "@/redux/store";
import LogInScreen from "@/screens/LogIn";
export default function Home() {
const { username, uid } = useAppSelector((state) => state.authReducer.value);
return (
<section className="m-4">
<h1 className="text-2xl">UserName : {username}</h1>
<h1 className="text-2xl">uid : {uid}</h1>
<LogInScreen name={username} />
</section>
);
}
예시로 만든 화면이다. 초기 상태에 유저 이름과 아이디가 모두 ""이기 때문에 비어있는 채로 나온다.
이제 input에 변수를 입력하고 로그인 버튼을 누르면?
dispatch와 useSelector가 열일하고 있음을 알 수 있다!
useAppSelector 는 어디에 있나용?