📡 Context API
- 리액트에 내장되어 있는 라이브러리
- 리액트 컴포넌트를 같은 문맥(Context)으로 묶어, 데이터 공유를 위한 일관된 인터페이스 제공
1. 타입 생성
interface User {
name: string;
age: number;
}
interface AuthContextType {
user: User | null;
isLoggedIn: boolean;
login: (user: User) => void;
logout: () => void;
}
2. Context 객체 생성
import { createContext } from "react";
export const AuthContext = createContext<AuthContextType | null>(null);
3. 생성한 Context 객체의 Provider 생성
export default function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const login = (user: User) => {
setUser(user);
setIsLoggedIn(true);
};
const logout = () => {
setUser(null);
setIsLoggedIn(false);
};
return (
<AuthContext.Provider value={{ user, isLoggedIn, login, logout }}>
{children}
</AuthContext.Provider>
);
}
4. 전역 상태 공유
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import AuthProvider from "./contexts/providers/AuthProvider.tsx";
createRoot(document.getElementById("root")!).render(
<AuthProvider>
<App />
</AuthProvider>
);
5. 필요 시 사용
- useContext 훅을 사용하여 필요한 컨텍스트 호출
import React, { useContext } from "react";
import { AuthContext } from "../contexts/AuthContext";
export default function AuthCheck({ children }: { children: React.ReactNode }) {
const { isLoggedIn, login, logout } = useContext(AuthContext)!;
return (
<>
{isLoggedIn && children}
{!isLoggedIn && (
<button onClick={() => login({ name: "James", age: 20 })}>
로그인
</button>
)}
{isLoggedIn && <button onClick={logout}>로그아웃</button>}
</>
);
}
번외 - Context API에 메모이제이션 적용
1) value를 다루는 함수에 메모이제이션 적용
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import CounterProvider from "./context/provider/CounterProvider.tsx";
createRoot(document.getElementById("root")!).render(
<CounterProvider>
<App />
</CounterProvider>
);
import { createContext } from "react";
interface CounterContextType {
count: number;
}
interface CounterActionContextType {
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const CounterContext = createContext<CounterContextType | null>(null);
export const CounterActionContext =
createContext<CounterActionContextType | null>(null);
import { ReactNode, useMemo, useState } from "react";
import { CounterActionContext, CounterContext } from "../CounterContext";
export default function CounterProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
const increment = () => { setCount((count) => count + 1); };
const decrement = () => { setCount((count) => count + 1); };
const reset = () => { setCount(0); };
const memo = useMemo(() => ({ increment, decrement, reset }), []);
return (
<>
<CounterActionContext.Provider value={ memo }>
<CounterContext.Provider value={{ count }}>
{children}
</CounterContext.Provider>
</CounterActionContext.Provider>
</>
);
}
2) 잘못된 메모이제이션
const increment = useCallback(() => {
setCount((count) => count + 1);
}, []);
const decrement = useCallback(() => {
setCount((count) => count + 1);
}, []);
const reset = useCallback(() => {
setCount(0);
}, []);
return (
<>
<CounterActionContext.Provider value={{ increment, decrement, reset }}>
<CounterContext.Provider value={{ count }}>
{children}
</CounterContext.Provider>
</CounterActionContext.Provider>
</>
);
- 기본적인 설정과 코드량이 많아 다소 러닝 커브가 높은 전역 상태 관리 라이브러리
- toolkit으로 redux를 쉽게 사용할 수 있음
- 크게 2가지로 구분 ⇒ 상태, 액션(상태를 변경하기 위해 조작하는 함수)
- 슬라이스 안에 상태와 액션을 정의해서 사용 (슬라이스는 여러 개 있을 수 있음)
1. 슬라이스 생성하기
- createSlice : 슬라이스에 필요한 액션 생성자(actions)와 리듀서(reducer) 생성
import { createSlice } from "@reduxjs/toolkit";
interface User {
name: string;
agE: number;
}
const authSlice = createSlice({
name: "authSlice",
initialState: {
user: null as User | null,
isLoggedIn: false,
},
reducers: {
login: (state, action) => {
state.user = action.payload;
state.isLoggedIn = true;
},
logout: (state) => {
state.user = null;
state.isLoggedIn = false;
},
},
});
export const { login, logout } = authSlice.actions;
export default authSlice.reducer;
2. 스토어 생성하기
- configure : configurre 객체로 스토어 생성
- reducer : 여러 개의 슬라이스를 결합하여 하나의 스토어로 관리할 수 있도록
import { configureStore } from "@reduxjs/toolkit";
import authSlice from "./slice/authSlice";
import { counterSlice } from "./slice/counterSlice";
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
auth: authSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
3. 필요 시 사용
- 변수: useSelector를 사용하여 리듀서에 접근 -> 원하는 슬라이스로 접근
- 함수: useDispatch로 원하는 함수 사용
export default function AuthCheck({ children }: { children: React.ReactNode }) {
const user = useSelector((state: RootState) => state.auth.user);
const isLoggedIn = useSelector((state: RootState) => state.auth.isLoggedIn);
const dispatch = useDispatch<AppDispatch>();
return (
<>
{isLoggedIn && user?.name}
{isLoggedIn && children}
{!isLoggedIn && (
<button onClick={() => dispatch(login({ name: "James", age: 20 }))}>
로그인
</button>
)}
{isLoggedIn && (
<button onClick={() => dispatch(logout())}>로그아웃</button>
)}
</>
);
}
📡 Zustand
- 사용법 매우 간단. 로직도 간단. 가벼움
- 리액트 훅처럼 선언하여 사용
- 리액트에 종속적인 것이 아닌 JS를 위한 라이브러리
- 앞으로 더 인기가 많아질 라이브러리가 될 것
1. 상태 store 생성하기
- 상태 store : 공유되는 상태를 저장하고 관리하는 중앙 저장소를 의미함
- 전역에서 접근 가능
- 상태를 변경하는 로직을 스토어 안에 두어 관리
interface CounterStoreType {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterStoreType>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
2. 상태 store 사용하기
export default function App() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
return (
<>
<h1>Count: {count}</h1>
<button onClick={decrement}>감소</button>
<button onClick={increment}>증가</button>
</>
);
;
3. 잘못된 상태 구독
const { count, increment, decrement } = useCounterStore();
- 위의 방식은 상태 store를 전체 구독하는 방식
- 상태가 변경될 때마다 스토어 객체 전체가 새로운 참조값을 가지므로 컴포넌트의 리렌더링 발생
- 이로 인해 컴포넌트에 React.memo 처리를 한 경우, 무의미해짐
📌 출처
수코딩(https://www.sucoding.kr)