로그인 여부를 원하는 컴포넌트에서 사용할 수 있게 redux-toolkit을 사용하여 isLogin을 전역변수로 뺄 수 있다. 또한 매번 타입을 지정해줘야하는 불편함을 제거하기 위해 custom hook을 만들것이다.
설치하기
npm install @reduxjs/toolkit react-redux
폴더 구조
hooks와 store 폴더의 구조를 살펴본다.
store 생성하기
store는 리덕스 저장소이며 각 리듀서를 저장하는 공간이다.
store/index.tsx 파일에 다음과 같이 작성한다.
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer: {} // 객체 내부에는 이 후에 작성한다.
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
리액트에 store을 등록
리액트에서 store를 사용할 수 있도록 src/index.tsx에 등록한다.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
slice 생성하기
slice는 store에 저장되는 리듀서를 "기능별로 슬라이스한 것"이라고 생각하면 편하다.
로그인 슬라이스, 모달창 슬라이스 등 기능별로 분리하여 작성한다.
loginSlice.tsx 파일에 로그인 여부라는 전역 상태를 만들고 로그인, 로그아웃 시 true, false를 인자로 받아 로그인 여부를 변경시키는 함수를 만들었다.
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface ILoginState {
isLogin: boolean;
}
const initialState: ILoginState = {
isLogin: false,
};
export const loginSlice = createSlice({
name: "login",
initialState,
reducers: {
setLogin: (state, { payload }: PayloadAction<boolean>) => {
state.isLogin = payload;
},
},
});
export const { setLogin } = loginSlice.actions;
export default loginSlice.reducer;
⭐ 마지막에 setLogin 함수와 reducer를 빼먹지 않고 내보내야 한다. 현재에는 reducers에 setLogin 함수밖에 없지만 추가할 경우 {setLogin, 추가 함수} 형식으로 내보내야한다.
또한 reducer는 store에 등록할 때 사용된다.
여러개의 reducer를 하나의 reducer로 합치기
여러개의 slice가 있을 경우 여러개의 reducer가 생긴다. 지금은 하나의 loginReducer밖에 없지만 모달창, 로그 기능 슬라이스 등을 만들면 그 만큼 reducer도 늘어날 것이다.
따라서 store에 reducer을 등록하기 전 reducer/reducer.tsx 라는 별도의 파일을 생성하여 reducer을 합쳐준다.
import loginSlice from "../slices/loginSlice";
const reducer = {
login: loginSlice,
};
export default reducer;
store에 reducer 등록하기
다시 store/index.tsx 파일로 돌아와서 reducer : {} 남겨두었던 부분을 reducer : reducer(합친 리듀서)로 작성한다.
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer/reducer";
const store = configureStore({
reducer, // == (reducer: reducer)
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;
redux hook 생성하기
훅을 사용하는 이유는 보다 간편하게 useSelector, useDispatch를 사용하기 위함이다.
- useSelector: 전역 상태를 가져온다.
- useDispatch: 함수를 호출한다.
- 만약 타입스크립트가 아닌 자바스크립트일 경우 useSelector 문법은 다음과 같다.
- 이것을 타입스크립트로 나타내면 state의 타입을 매번 지정해줘야하는 불편함이 있다.const count = useSelector((state) => state.login.isLogin);
const count = useSelector((state: RootState) => state.login.isLogin);
따라서 다음과 같이 훅을 만든다.
import { TypedUseSelectorHook, useSelector, useDispatch } from "react-redux";
import { RootState, AppDispatch } from "../store";
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useTypedDispatch = () => useDispatch<AppDispatch>();
나는 타입을 미리 명시한 useTypedSelector, useTypeDispatch 훅을 만들었다.
컴포넌트에서 사용하기
카카오에 로그인 요청 시 Oauth 컴포넌트가 실행되고 인가코드를 받아 우리 서비스의 백엔드에 인가코드를 넘기고, 성공하면 로그인 여부를 true로 변경시키는 스켈레톤 코드이다.
- dispatch(setLogin(true))로 상태를 변경하면 state.login을 사용하는 모든 컴포넌트에서 재 렌더링이 된다.
import React, { useEffect } from "react";
import { useTypedDispatch, useTypedSelector } from "../hooks/redux";
import { setLogin } from "../store/slices/loginSlice";
const Oauth = () => {
const code = new URL(document.location.toString()).searchParams.get("code");
const login = useTypedSelector((state) => state.login);
const dispatch = useTypedDispatch();
useEffect(() => {
console.log(code);
// 백엔드에 code(인가 코드) 넘기기
// 성공하면 isLogin
dispatch(setLogin(true));
}, [code, dispatch]);
console.log("렌더링!");
return (
<div>
<div>Login Status: {login.isLogin ? "Logged In" : "Not Logged In"}</div>
</div>
);
};
export default Oauth;
+ 추가적으로 리덕스의 재렌더링에 대해서도 공부하였다.
구독하는 상태 슬라이스에 따른 재렌더링
- 상태 구독('useSelector') : 컴포넌트가 'useSelector' 훅을 사용하여 특정 상태 조각을 구독하고 있는 경우, 그 상태 조각이 변경될 때만 해당 컴포넌트가 재렌더링된다.
- 상태 참조 분리 : 다른 상태 조각을 구독하는 컴포넌트는 영향을 받지 않음
💬 예를 들어 로그인 슬라이스와 모달창 슬라이스가 있고 로그인 슬라이스 내에 로그인 여부, 모달창 슬라이스 내에 모달창 열림 여부가 있다고 가정
- state.login.isLogin이 변경된다고 해서 state.modal.isOpen을 참조하는 컴포넌트가 렌더링되지 않음
- state.login.isLogin과 state.login.id는 같은 login 슬라이스를 참조하기 때문에 state.login을 구독하는 모든 컴포넌트가 재렌더링됨