Post

πŸ₯œ [React] μƒνƒœκ΄€λ¦¬ 라이브러리 λΉ„κ΅ν•˜κΈ° (ContextAPI, Redux, Recoil, Zustand)

μ™œ?

μƒνƒœκ΄€λ¦¬λž€?

ν”„λ‘ νŠΈμ—”λ“œμ—μ„œ stateλŠ” Renderingν•˜λŠ”λ° μžˆμ–΄μ„œ 영ν–₯을 λ―ΈμΉ  수 μžˆλŠ” κ°’

μƒνƒœλ₯Ό μ–΄λ–»κ²Œ κ΄€λ¦¬ν•˜λŠλƒμ— 따라 λ‘œλ”©μ†λ„μ™€ μ‚¬μš©κ° κ°œμ„ μ΄ ν™•μ—°νžˆ 차이가 λ‚œλ‹€.

Frontend κ°œλ°œμžμ—κ²Œ μƒνƒœκ΄€λ¦¬λŠ” μ€‘μš”ν•œ μž„λ¬΄κ°€ λ˜μ—ˆλ‹€.

λ”°λΌμ„œ νšŒμ‚¬λ§ˆλ‹€ μƒνƒœκ΄€λ¦¬ ν•˜λŠ” 법도 제각기 λ‹€λ₯΄κΈ° 떄문에

항상 μ‚¬μš©ν•˜λ˜ μƒνƒœκ΄€λ¦¬ 라이브러리 외에 λ‹€λ₯Έ 라이브러리λ₯Ό

μ‚¬μš©ν•΄λ³΄κ³ μž μ˜€λŠ˜μ€ μƒνƒœκ΄€λ¦¬ λΌμ΄λΈŒλŸ¬λ¦¬λ“€μ„ μ•Œμ•„λ³Ό 생각이닀.



ContextAPI

image-context-api

λ¨Όμ € μ•Œμ•„λ³Ό 것은 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 λ₯Ό 감싼닀!

}
  1. λ¨Όμ €, global-state λ₯Ό 관리 ν•  Provider λ₯Ό μƒμ„±ν•œλ‹€.

  2. global-state λ₯Ό 담을 context λ₯Ό 생성 ν•œ ν›„

  3. 후에 뿌렀질 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

image-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

image-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

image-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 도 μ‚¬μš©ν•˜λŠ” 곳이 λ§Žμ•„ μ œλŒ€λ‘œ λ°°μ›Œμ•Ό ν•˜λŠ”λ° 은근 어렡더라

This post is licensed under CC BY 4.0 by the author.