π₯ [React] μνκ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬ λΉκ΅νκΈ° (ContextAPI, Redux, Recoil, Zustand)
μ?
μνκ΄λ¦¬λ?
νλ‘ νΈμλμμ
state
λRendering
νλλ° μμ΄μ μν₯μ λ―ΈμΉ μ μλ κ°
μνλ₯Ό μ΄λ»κ² κ΄λ¦¬νλλμ λ°λΌ λ‘λ©μλμ μ¬μ©κ° κ°μ μ΄ νμ°ν μ°¨μ΄κ° λλ€.
Frontend κ°λ°μμκ² μνκ΄λ¦¬λ μ€μν μλ¬΄κ° λμλ€.
λ°λΌμ νμ¬λ§λ€ μνκ΄λ¦¬ νλ λ²λ μ κ°κΈ° λ€λ₯΄κΈ° λλ¬Έμ
νμ μ¬μ©νλ μνκ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬ μΈμ λ€λ₯Έ λΌμ΄λΈλ¬λ¦¬λ₯Ό
μ¬μ©ν΄λ³΄κ³ μ μ€λμ μνκ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬λ€μ μμλ³Ό μκ°μ΄λ€.
ContextAPI
λ¨Όμ μμλ³Ό κ²μ ContextAPI
. μ λ§ μ€λλ κΈ°μ μ΄λ€.
κ·Έλ§νΌ React
λ΄μ μ체μ μΌλ‘ λ΄μ₯ λμ΄μλ κΈ°μ μ΄λ€. μ°κΈ°λ λμμ§ μμ νΈμ΄μ§λ§
λ¨μ μ νμ νλλ‘ νκ² λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// GlobalProvider.tsx
import { ReactNode, createContext, useState } from "react";
interface IContextProps {
counter: number;
onPlus: () => void;
} // μ μλ³μμ νμ
μ§μ
interface IProviderProps {
children: ReactNode;
} // μ μλ³μλ₯Ό κ°μ Έλ€ μΈ children-component μ type μ€μ
export const CounterContext = createContext<IContextProps>({
counter: 0,
onPlus: () => {},
}); // μ μλ³μλ₯Ό λ΄μ Context λ₯Ό μμ± λ° μ΄κΈ°κ° μ§μ
export default function GlobalProvider({ children }: IProviderProps) {
const [counter, setCounter] = useState<number>(0);
const onPlus = () => {
setCounter((current: number) => (current += 1));
};
return (
<CounterContext.Provider value={{ counter, onPlus }}>
{children}
</CounterContext.Provider>
); // μ μλ³μλ₯Ό λ΄κ³ μλ CounterContext.Provider λ‘ children-component λ₯Ό κ°μΌλ€!
}
λ¨Όμ , global-state λ₯Ό κ΄λ¦¬ ν Provider λ₯Ό μμ±νλ€.
global-state λ₯Ό λ΄μ context λ₯Ό μμ± ν ν
νμ λΏλ €μ§ children-component μ κ°μΈμ€λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.tsx (index.tsx)
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import GlobalProvider from "./GlobalProvider.tsx";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<GlobalProvider>
<App />
</GlobalProvider>
</BrowserRouter>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// test01.tsx
import { useContext } from "react";
import { CounterContext } from "./GlobalProvider";
export default function Text1() {
const { counter, onPlus } = useContext(CounterContext);
// Context λ₯Ό μ¬μ©νκΈ° μν΄ useContext λ₯Ό μ΄μ©ν΄ CounterContext λ₯Ό μ°κ²°
return (
<div>
<p>COUNT : {counter}</p>
<button onClick={onPlus}>+++</button>
</div>
);
}
ContextAPI λ React μ μ체 λ΄μ₯λμ΄μλ€λ κ²λ§μΌλ‘λ μΆ©λΆν 맀λ ₯μ μΈλ°
λ¨μ μ΄ μμλ€.
νλ² μμ λλ©΄ Provider λ΄ λͺ¨λ Components κ° re-rendering λλ€λ κ²β¦
λ°λΌμ λΆνμν rendering μ΄ λ§μμ§λ€.
(μ΄ μμ μ¨ μ²λΌ λλ€β¦ μ λλλ°)
Redux
λ§μ΄λ€ μ¬μ©νλ redux
. μ¬μ©νλ μ¬λλ§λ€ μ°λλ²μ΄ λ€μνλ°
μλλ©΄ Redux
μ체κ°
μ¬λ¬ μΆκ° λΌμ΄λΈλ¬λ¦¬κ° λ§κΈ° λλ¬Έμ΄λ€.
κ·Έλ‘ μΈν΄ μ¬μ©νκΈ°κ° μκ·Ό κΉλ€λ‘μΈ μ λ
μ΅λν κ°λ¨νκ² λ§λ€μ΄λ³΄μλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// store.ts
import { legacy_createStore as createStore } from "redux";
// μ‘μ
νμ
μ μ
const INCREMENT = "INCREMENT";
// μ‘μ
μμ±μ
export const incrementSummary = () => ({ type: INCREMENT });
// μ΄κΈ° μν μ μ
interface AppState {
summary: number;
}
const initialState: AppState = {
summary: 0,
};
// 리λμ ν¨μ
const appReducer = (state = initialState, action: any) => {
switch (action.type) {
case INCREMENT:
return {
...state,
summary: state.summary + 1,
};
default:
return state;
}
};
// μ€ν μ΄ μμ±
const store = createStore(appReducer);
export default store;
μνκ΄λ¦¬λ₯Ό νκΈ° μν store λ₯Ό μμ±ν΄μ€λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// main.tsx
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./utils/store.tsx";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
);
μ΅μλ¨μ Provider λ‘
store
λ₯Ό κ±Έμ΄μ state λ₯Ό μ 곡ν΄μ€λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Test.tsx
import { useDispatch, useSelector } from "react-redux";
import { incrementSummary } from "./utils/store";
export default function Test() {
const dispatch = useDispatch(); // μ‘μ
μ λμ€ν¨μΉν μ μλ ν¨μ
const summary = useSelector((state: any) => state.summary);
// useSelector μ¬μ©νμ¬ Redux μ€ν μ΄μ μνλ₯Ό μ ν
return (
<div>
<h1>Summary: {summary}</h1>
<button onClick={() => dispatch(incrementSummary())}>Increment</button>
</div>
);
}
κ°μΈμ μΌλ‘ λ무 μ΄λ ΅λ€.
Recoil
λ΄κ° μ μΌ μμ£Ό μ°λ λΌμ΄λΈλ¬λ¦¬, κ°λ¨ν νλ‘μ νΈμ μμ£Ό μ μ©νλ€κ³ μκ°νλ€.
1
2
3
4
5
6
7
8
9
10
11
12
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import { BrowserRouter } from "react-router-dom";
import { RecoilRoot } from "recoil";
ReactDOM.createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<RecoilRoot>
<App />
</RecoilRoot>
</BrowserRouter>
);
λ¨Όμ Recoil μ μ¬μ©νκΈ° μν΄ μ΅μλ¨μ RecoilRoot λ₯Ό κ±Έμ΄μ€λ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { atom } from "recoil";
export const summaryState = atom({
key: "summaryState",
default: 0,
}); // κ³ μ ν€ κ°κ³Ό κΈ°λ³Έ κ° μ μ
import { selector } from 'recoil';
const charCountState = selector({
key: 'charCountState',
get: ({get}) => {
const text = get(textState);
return text.length;
},
}); // atom κ°μ μ§μ λ°κΏ νμμνλ‘ μ¬μ©λ κ°λ₯
atom μΌλ‘ κ³ μ key κ°κ³Ό initial-state λ₯Ό μ§μ νλ€
selector λ₯Ό μ¬μ©ν΄ λ€λ₯Έ κ°μ νμμνλ‘ μ¬μ©λ κ°λ₯νλ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useRecoilState } from "recoil";
import { summaryState } from "./utils/atoms";
export default function Test() {
const [summary, setSummary] = useRecoilState(summaryState);
const onPlus = () => {
setSummary((current) => (current += 1));
};
return (
<div>
<h2>{summary}</h2>
<button onClick={onPlus}>+++</button>
</div>
);
}
μ¬μ©μμλ λ§€μ° κ°λ¨νκ²
useState()
μ²λΌ μ¬μ©μ΄ κ°λ₯νλ€!
Zustand
zustand
λ λ
μΌμ΄λ‘ μν
λΌλ λ»μ΄λ€.
μ¬ν μκ°ν λ€λ₯Έ λΌμ΄λΈλ¬λ¦¬λ³΄λ€ κ°λ²Όμ°λ©° μ°κΈ°λ λ§€μ° νΈν μΆμ μνλ€.
μ½λλ₯Ό λ¨Όμ μμ보μ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// util/store.ts
import { create } from "zustand";
interface IStore {
count: number;
increment: () => void;
}
export const useStore = create<IStore>()((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
1
2
3
4
5
6
7
8
9
10
11
12
13
// test.tsx
import { useStore } from "./utils/store";
export default function Test() {
const { count, increment } = useStore();
return (
<div>
<span>{count}</span>
<button onClick={increment}>+++</button>
</div>
);
}
λ€λ₯Έ λΌμ΄λΈλ¬λ¦¬ μ²λΌ
main.tsx
μProvider
κ°μκ±Έ κ±Έμ§ μμλ λλ€ !
λ§μΉλ©°
zustand
κ° μκ°λ³΄λ€ λ§€μ° κ°λ³κ³ κ°λ¨ν΄μ λ§μ λ€μλ€.
λ€μ νλ‘μ νΈ ν λ μ¨λ΄μΌκ² λ€.
Redux
λ μ¬μ©νλ κ³³μ΄ λ§μ μ λλ‘ λ°°μμΌ νλλ° μκ·Ό μ΄λ ΅λλΌ