๐ ๋ค์ด๊ฐ๋ฉฐ
์ด๋ฒ ํฌ์คํ ์์๋ React์์ ์ฌ์ฉ๋๋ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํด์ ๊ฐ๊ฐ์ ํน์ง๊ณผ ๊ฐ์ธ์ ์ธ ๊ฒฌํด๊ฐ ๋ค์ด๊ฐ ์์ต๋๋ค.
ํ์ฌ ํฌ์คํ ์์ ๋ค๋ฃฐ ์ฃผ์ ๋ค ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
- react-redux & redux-toolkit
- recoil
- zustand
- jotai
React์์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ๊ฐ๋ฐ์ ํ๋ค ๋ณด๋ฉด ์์์ ์ธ๊ธํ ๋ค ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ํ๋์ฏค์ ๋ถ๋ช ์๊ณ ๊ณ์๋ฆฌ๋ผ ์๊ฐํฉ๋๋ค.
์ ์ด๋ฏธ์ง๋ ์ต๊ทผ 1๋ ๊ฐ ๋ค ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ NPM ๋ํฅ์ ๋๋ค.
ํ์ฌ ์ถ์ธ๋ก ๋ดค์ ๋
1์: redux (react-redux & redux-tookit)
2์: zustand
3์: jotai
4์: recoil
์ ์์๋ก ๋ณด์ ๋๋ค.
๊ทธ๋ผ ๊ฐ๋จํ ์ฝ๋ ์์ ์ ํจ๊ป ํน์ง์ ์์๋ณด๊ฒ ์ต๋๋ค.
Redux (react-redux & redux-toolkit)
Redux์ ์ฃผ์ ์์ด๋์ด๋ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๊ฐ ๋จ์ผ ์ ์ญ "store"์ ์ ์ฅ๋๊ณ ์ํ ์์ ์ ์ ์ฅ์์ ์ ๋ฌ๋๋ "action"์ ์ฌ์ฉํ์ฌ ์ด๋ฃจ์ด์ง๋๋ค.
๊ทธ๋ฐ ๋ค์ ์ ์ฅ์๋ ์ด๋ฌํ ์์ ์ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ์ ๋ฐ์ดํธํ๊ณ ์ด๋ฅผ ํ์๋ก ํ๋ React ๊ตฌ์ฑ ์์์ ์ ๋ฐ์ดํธ๋ ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
์ด ์ํคํ ์ฒ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ๋ ์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์์ผ๋ฉฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋์์ ์์ธก ๊ฐ๋ฅํ๊ณ ์ฝ๊ฒ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
Redux๋ Flux ํจํด์ ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๋ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ฌ๊ธฐ์๋ redux-toolkit์ ๊ธฐ์ค์ผ๋ก ์์๋ณด๊ฒ ์ต๋๋ค.
redux-toolkit์ ๊ธฐ์ค์ผ๋ก ์์๋ณด๋ ์ด์ ๋ redux ๊ณต์๋ฌธ์์์๋ redux-toolkit์ ์ถ์ฒํ๊ณ ์๊ณ , ๊ธฐ์กด์ redux์ ๋ฌธ์ ์ ์ ๋ณด์ํ๋ฉด์ ํจ์จ์ ์ธ redux ๊ฐ๋ฐ์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ๋๋ค.
๊ธฐ์กด์ ๋ฌธ์ ์
- Redux ์คํ ์ด์ ๊ตฌ์ฑ์ด ๋ณต์กํจ
- Redux๋ก ์ ์ฉํ ์์ ์ ์ํํ๊ธฐ ์ํด์๋ ๋ง์ ํจํค์ง๊ฐ ์ถ๊ฐ๋ก ํ์ํจ
- ๋ถํ์ํ๊ฒ ๋ง์ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋
redux-tookit์ผ๋ก ์์ฑํ counter ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋ ์์ ์ ๋๋ค.
redux๋ ์๋์ ๊ฐ์ด Provider๋ก ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ์ค ๋ค store๋ฅผ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { store } from './store';
import { Provider } from 'react-redux';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
์ดํ, ์๋์ ๊ฐ์ด slice๋ฅผ ์์ฑํ์ฌ reducer๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
//features/counter/countSlice.tsx
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
export interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
//store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counter/counterSlice';
export const store = configureStore({
reducer: { counter: counterReducer },
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
์์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ ๋ค์, ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
// App.tsx
import './App.css';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from './store';
import { decrement, increment } from './features/counter/counterSlice';
function App() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
const onIncrementButtonClick = () => dispatch(increment());
const onDecrementButtonClick = () => dispatch(decrement());
return (
<>
<h1>๋ฆฌ์กํธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ </h1>
<div className="card">
<button onClick={onIncrementButtonClick}>Increment value</button>
</div>
<span>{count}</span>
<div className="card">
<button onClick={onDecrementButtonClick}>Decrement value</button>
</div>
</>
);
}
export default App;
ํน์ง ๐ง
๊ฐ๋จํ counter ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐ ์์ด์ ๊ทธ๋๋ ์ฝ๋๋์ด ๋ง์ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋์ ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค redux๊ฐ ๊ฐ์ฅ ์ญ์ฌ๊ฐ ์ค๋๋์๊ณ , redux์ ์๊ฐ์ ๋ฐ์ ํ์๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์กด์ฌํฉ๋๋ค.
์ด ๋ป์ ๊ทธ๋งํผ ๊ฒ์ฆ๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ณ , redux๋ก ๊ฐ๋ฐํ ๋ ์ฐธ๊ณ ์๋ฃ๊ฐ ์ ๋ง ๋ง์ต๋๋ค.
ํ์ง๋ง Redux ์์ฒด์ ๊ฐ๋ ์ ์ดํดํ๋ ๋ฐ ๋ฌ๋ ์ปค๋ธ๋ ์๋์ ์ผ๋ก ๋์ต๋๋ค.
๋์ , Flux ํจํด์ ์ดํดํ๋ ๋ฐ ๋ง์ ๋์์ด ๋๋ค๊ณ ์๊ฐํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ Redux-toolkit ์ญ์ Redux์ ๊ธฐ๋ณธ์ ์์์ผ ํจ๊ณผ์ ์ผ๋ก ์ ์ฌ์ฉํ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
๋ํ, redux-toolkit์ RTK-query(์๋ฒ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ)๋ ๋ด์ฅ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ณ๋์ ์ค์น ์์ด ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๋ ๋ฐ์ ์์ด์ redux์ ์ ํธํ์ด ๋ฉ๋๋ค.
ํ์ง๋ง ๋ฐ๋๋ก ์๊ฐํด ๋ณด๋ฉด ์๋ฒ ๋ฐ์ดํฐ์ ๋ํ ์ํ ๊ด๋ฆฌ๊ฐ ํ์ ์์ ๋์๋ ํจํค์ง ์ฉ๋๋ง ๋ถํ์ํ๊ฒ ์ฆ๊ฐํ๋ ๊ผด์ด ๋์ด๋ฒ๋ฆฌ์ฃ .
Redux ์์ฒด๊ฐ ๋ค๋ฅธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋นํด์ ํจํค์ง ์ฉ๋์ด ํด๋ฟ๋๋ฌ, ๋ ์ด๋ฌํ ๋จ์ ์ ๊ฐ์ ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ๋ฑ์ฅํ๋ฉด์ ์ต๊ทผ ๋ํฅ์ผ๋ก ๋ดค์ ๋, redux๋ก ์์ฑ๋ ์ฝ๋๋ค์์ redux๋ฅผ ๊ฑท์ด๋ด๊ณ ์๋ ์ถ์ธ์ธ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋นผ๋์ ์ ์๋ ํฐ ์ฅ์ ์ค ํ๋๋ redux-dev-tools๋ฅผ ํตํด์ ์ํ ๊ด๋ฆฌ์ ๋๋ฒ๊น ์ ์์ด์ ๋งค์ฐ ํธ๋ฆฌํฉ๋๋ค.
npm์์ ํ์ธํ ๊ฒฐ๊ณผ๋ก๋ ์ฉ๋์ 5.51MB๋ฅผ ๊ฐ์ง๊ณ ์๊ณ , ์ต๊ทผ ๋ฐฐํฌ๋ 4์ผ ์ ์ ์งํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ฆ, ์ฉ๋์ ์๋์ ์ผ๋ก ํฌ๊ณ , ์ ์ง๋ณด์๋ ๊พธ์คํ ๋๊ณ ์๋ค๋ ๊ฑธ๋ก ํด์ํ ์ ์๊ฒ ๋ค์.
๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
- Redux๋ ์ถฉ๋ถํ ๊ฐ๋ ฅํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. (middleware, devTools ๋ฑ)
- ์ฐธ๊ณ ์๋ฃ๊ฐ ๋ง๋ค.
- ๋ฌ๋์ปค๋ธ๊ฐ ๋๋ค.
- redux-toolkit์ ์ฌ์ฉํด๋ ์ฝ๋๋์ด redux์ ๋นํด ์ ์ด์ง ๊ฒ์ด์ง ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๋นํด ์๋์ ์ผ๋ก ๋ง๋ค.
- Redux์ ๋จ์ ์ ๊ฑท์ด๋ผ ์ ์๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ๋ฑ์ฅํ๋ค.
Recoil
Recoil์ FaceBook(ํ Meta)์์ ๋ฐํํ React๋ฅผ ์ํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
React๋ฅผ ๊ฐ๋ฐํ ๊ธฐ์ ์์ ๊ฐ๋ฐํ๋ React๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ธฐ๋๊ฐ์ ๊ฐ์ก๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ฌ์ฉ ๋ฐฉ๋ฒ์ด ๋งค์ฐ ๊ฐ๋จํ๊ณ React Hooks์ ์ ์ฌํ๊ฒ ๋์ํ์ฌ React Hooks์ ์ต์ํ๋ค๋ฉด ๋ฌ๋ ์ปค๋ธ๊ฐ ๋งค์ฐ ๋ฎ์ต๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์ํ๋ฅผ ์์(atom)๋ผ๋ ์์ ๋จ์๋ก ์ทจ๊ธ์ ํ๋ฉฐ ์์ ์ํ๋ฅผ ์กฐํฉํ์ฌ ํฐ ์ํ๋ฅผ ๋ง๋ค๊ณ ,
์ํ์์ ๋ค๋ฅธ ์ํ๋ฅผ ํ์ํ๊ฑฐ๋ Selector์ ๊ฐ์ ์์ ํจ์๋ฅผ ์ ๊ณตํ๋ฉฐ bottom-up ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
์๋๋ ๊ฐ๋จํ counter ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋ ์์ ์ ๋๋ค.
Recoil ๋ํ Provider ์ปดํฌ๋ํธ๋ก ๊ฐ์ธ์ฃผ์ด์ผ ํฉ๋๋ค.
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { RecoilRoot } from 'recoil';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RecoilRoot>
<App />
</RecoilRoot>
</React.StrictMode>
);
atom ์ํ์ ๋จ์์ด๋ฉฐ, ์ ๋ฐ์ดํธ์ ๊ตฌ๋ ์ด ๊ฐ๋ฅํฉ๋๋ค.
๊ทธ๋์ atom์ด ์ ๋ฐ์ดํธ๋๋ฉด ๊ฐ๊ฐ ๊ตฌ๋ ๋ ์ปดํฌ๋ํธ๋ ์๋ก์ด ๊ฐ์ ๋ฐ์ํ์ฌ ๋ค์ ๋ ๋๋ง ๋ฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ตฌ๋ณํ๊ธฐ ์ํ ๊ณ ์ ์ key๊ฐ์ด ํ์ํฉ๋๋ค.
// features/counter/atom.ts
import { atom } from 'recoil';
export const countState = atom<number>({
key: 'countState',
default: 0,
});
์ฌ์ฉํ๋ ๊ณณ์์๋ useState์ ๊ฐ์ ๋ฐฉ์์ผ๋ก useRecoilState๋ฅผ ํตํด์ ์ํ๋ฅผ ์ ๋ฐ์ดํธํด์ค ์ ์์ต๋๋ค.
// App.tsx
import './App.css';
import { useRecoilState } from 'recoil';
import { countState } from './features/counter/atom';
function App() {
const [count, setCount] = useRecoilState(countState);
const onIncrementButtonClick = () => setCount((count) => count + 1);
const onDecrementButtonClick = () => setCount((count) => count - 1);
return (
<>
<h1>๋ฆฌ์กํธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ </h1>
<div className="card">
<button onClick={onIncrementButtonClick}>Increment value</button>
</div>
<span>{count}</span>
<div className="card">
<button onClick={onDecrementButtonClick}>Decrement value</button>
</div>
</>
);
}
export default App;
ํน์ง ๐ง
๋งค์ฐ ์ ์ ์ฝ๋๋ก ์ ์ญ ์ํ๋ก ๊ด๋ฆฌํ ์ ์์์ ํ์ธํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ง๊ด์ ์ธ ์ฝ๋๋ก ์ธํด์ ๋ณต์กํ ๋ก์ง์ ๋ํด์๋ ๊ฐ๋ ์ฑ์ ํฌ๊ฒ ๊ฐ์ ธ๊ฐ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค.
์ ์์ ์์๋ atom๋ง ์ฌ์ฉํ์ง๋ง recoil์ selectors๋ฅผ ์ด์ฉํด์ atom์ ๋ํ ํ์๋ ์ํ๋ฅผ ๋ง๋ค ์๋ ์์ต๋๋ค.
๋ํ, ๋น๋๊ธฐ ๋ฐ์ดํฐ์ ๊ด๋ จํด์๋ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
๋จ์ ์ผ๋ก๋ redux-dev-tools์ ๊ฐ์ ๋๋ฒ๊น ํด์ด ์๋ฒฝํ๊ฒ ์ง์๋์ง ์๋ ์ ์ด ์กด์ฌํฉ๋๋ค.
ํ์ง๋ง ํ์ฌ ๊ด๋ฆฌ๊ฐ ์ ๋๊ณ ์์ง ์์ต๋๋ค.
์ ๋ํ redux๋ฅผ ์ฌ์ฉํ ์ดํ recoil์ ๋ํ ์ฌ์ฉ ๊ฒฝํ์ด ๋งค์ฐ ์ข์์ recoil์ ์์ผ๋ก์ ๋ฐฉํฅ์ฑ์ ๋ํด์ ๋งค์ฐ ๊ธฐ๋ํ์ต๋๋ค.
2020๋ ์ ์ถ์ํ์ง๋ง ๋ฒ์ ๋ ๋งค์ฐ ๋ฎ์๋ฟ๋๋ฌ, ๋ง์ง๋ง ์ ๋ฐ์ดํธ๊ฐ 1๋ ์ ์ ๋๋ค.
์ด๋ฌํ ์ด์ ๋ก recoil์ ์ต๊ทผ ๋ค์ด๋ก๋ ํ์๋ ์ค์ด๋ค๊ณ ์์ต๋๋ค.
์ฃผ์ ์์ธ์ผ๋ก recoil์ ์ด๊ธฐ ์ค๊ณ๋ถํฐ ๊ฐ๋ฐ๊น์ง ๋ด๋นํ ๋ฉ์ธ ๊ฐ๋ฐ์๊ฐ ์ ๋ฆฌ ํด๊ณ ๋ฅผ ๋นํ ์ํฅ์ด ํฐ ๊ฒ ๊ฐ์ต๋๋ค.
recoil์ ์ญ์ฌ๊ฐ ์ค๋๋ ๊ฒ๋ ์๋๋ผ, ์ฐธ๊ณ ์๋ฃ๊ฐ ๋ถ์กฑํ ์ํฉ์ ๋๋ค.
์ด๋ฌํ ์ํฉ์์ ์ ๋ฐ์ดํธ๊ฐ ๋์ง ์์ผ๋ ์ฌ์ค์ ์์ผ๋ก recoil์ ์ ํํ ์ด์ ๋ ๋งค์ฐ ์ ๋ค๊ณ ์๊ฐ๋ฉ๋๋ค.
์ค์ ๋ก ์ดํ ์๋์์ ์ค๋ช ํ jotai์๊ฒ ๋ฐ๋ฆฌ๋ ์ค์ ์์ต๋๋ค.
Zustand
Zustand๋ ๋ ์ผ์ด๋ก '์ํ'๋ฅผ ์๋ฏธํ๋ค๊ณ ํ๋ค์.
ํ์ฌ ๋ ์ค๋ฅด๊ณ ์๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ํ๋์ ๋๋ค.
๊ฐ์ํ๋ Flux ํจํด์ ์ฌ์ฉํ๋ฉฐ ์๊ณ ๋น ๋ฅด๊ณ ํ์ฅ ๊ฐ๋ฅํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๋ค๋ฅธ React ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์คํ ์ด๋ฅผ ์ฃผ์ ํ ๋ Context API๋ฅผ ์ฌ์ฉํ์ง๋ง Zustand๋ ํด๋ก์ ๋ฅผ ํ์ฉํ์ฌ ์คํ ์ด ๋ด๋ถ ์ํ๋ฅผ ๊ด๋ฆฌํจ์ผ๋ก์จ ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ข ์์ ์ด์ง ์์ต๋๋ค
์๋๋ counter ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋ ์์ ์ ๋๋ค.
์๋์ ๊ฐ์ด devtools๋ก ๊ฐ์ธ์ฃผ๋ฉด redux-dev-tools๋ฅผ ํ์ฉํ์ฌ ๋๋ฒ๊น ์ด ๊ฐ๋ฅํฉ๋๋ค.
// features/counter/store.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface ICountStore {
count: number;
increment: () => void;
decrement: () => void;
}
export const useCountStore = create<ICountStore>()(
devtools((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
);
// App.tsx
import './App.css';
import { useCountStore } from './features/counter/store';
function App() {
const { count, increment, decrement } = useCountStore();
return (
<>
<h1>๋ฆฌ์กํธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ </h1>
<div className="card">
<button onClick={increment}>Increment value</button>
</div>
<span>{count}</span>
<div className="card">
<button onClick={decrement}>Decrement value</button>
</div>
</>
);
}
export default App;
ํน์ง ๐ง
Zustand๋ Provider๋ก ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ์ฃผ์ง ์์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
๋ํ, ์ํ์, ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์ก์ ์ ์ ์ํ๊ณ , ๋ฆฌํด ๋ฐ์ hook์ ์ด๋ ์ปดํฌ๋ํธ์์๋ import ํ์ฌ ์ํ๋ ๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
Redux์ ๊ฐ์ Flux ํจํด์ผ๋ก ๋์ํ๋๋ฐ, ๋งค์ฐ ์ ์ ์ฝ๋์์ผ๋ก ๊ฐ๋ฐ์ด ๊ฐ๋ฅํฉ๋๋ค.
NPM ๊ธฐ์ค ์ฉ๋์ 327kb๋ก ๋งค์ฐ ์ ์ต๋๋ค.
์ต๊ทผ ์ ๋ฐ์ดํธ๋ 2์ผ ์ ์ด๊ณ ์์ฃผ ํ๋ฐํ๊ฒ ์ ๋ฐ์ดํธ๊ฐ ์งํ๋๊ณ , ์ฃผ๊ฐ ๋ค์ด๋ก๋ ํ์๋ ๋์ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
Zustand๊ฐ Redux์ ContextAPI ๋ณด๋ค ๋์ ์ด์ ๋ฅผ ์๋์ ๊ฐ์ด ์ค๋ช ํ๊ณ ์์ต๋๋ค.
- ๋งค์ฐ ๊ฐ๋จํจ.
- React Hook์ ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ถ๊ฐ์ ์ธ Hook์ด ํ์ ์์
- Provider๋ก ๊ฐ์ธ์ง ์์๋ ๋๊ธฐ ๋๋ฌธ์ ๊ตฌ์กฐ๊ฐ ๋ ๋จ์ํจ.
- ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ๊ฐ์
- ์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง โก๏ธ ๋ถํ์ํ ๋ ๋๋ง ๊ฐ์
- ์ค์ ์ง์ค์, ์ก์ ๊ธฐ๋ฐ ์ํ ๊ด๋ฆฌ โก๏ธ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ ์ฒด๊ณ์ ์ด๊ณ ์ผ๊ด์ฑ ์๊ฒ ๋ง๋ค์ด ์ค
Flux ํจํด์ ์ต์ํ๋ค๋ฉด ์ ์ ์ฉ๋๊ณผ ์ ์ ์ฝ๋๋ก ๋น ๋ฅด๊ฒ ๊ฐ๋ฐ์ด ๊ฐ๋ฅํ Zustand์ ์ฌ์ฉ์ ๊ณ ๋ คํด๋ณด์ง ์์ ์๊ฐ ์๊ฒ ๋ค์.
Jotai
Jotai๋ Recoil์ ์๊ฐ์ ๋ฐ์์ ๋ฑ์ฅํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฉฐ ํ์ฌ ๋ ์ค๋ฅด๊ณ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๊ทธ๋์ Jotai ๋ํ atomic ํจํด์ ๋ฐ๋ฅด๊ณ , React๋ฅผ ์ํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ฉฐ React Hooks์ ๋งค์ฐ ์ ์ฌํ๊ฒ ๋์ํฉ๋๋ค.
์๋๋ ์ญ์ counter ๊ธฐ๋ฅ์ ํ๋ ์ฝ๋ ์์ ์ ๋๋ค.
// features/counter/atom.ts
import { atom } from 'jotai';
export const countAtom = atom(0);
// App.tsx
import './App.css';
import { useAtom } from 'jotai';
import { countAtom } from './features/counter/atom';
function App() {
const [count, setCount] = useAtom(countAtom);
const onIncrementButtonClick = () => setCount((count) => count + 1);
const onDecrementButtonClick = () => setCount((count) => count - 1);
return (
<>
<h1>๋ฆฌ์กํธ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ </h1>
<div className="card">
<button onClick={onIncrementButtonClick}>Increment value</button>
</div>
<span>{count}</span>
<div className="card">
<button onClick={onDecrementButtonClick}>Decrement value</button>
</div>
</>
);
}
export default App;
ํน์ง ๐ง
์ฝ๋๋ฅผ ๋ณด๋ฉด Recoil ๊ทธ๋ฆฌ๊ณ React Hooks์ ๋งค์ฐ ์ ์ฌํฉ๋๋ค.
๋ํ, Recoil๊ณผ ๋ค๋ฅธ ์ ์ atom ์์ฑ ์ key๊ฐ ํ์ํ์ง ์๊ณ , Provider ๋ํ ํ์ํ์ง ์์ต๋๋ค.
Provider ๊ด๋ จํด์๋ ํ์์ ๋ฐ๋ผ ์ฌ์ฉํ์ฌ ๊ฐ์ธ์ค ์ ์์ต๋๋ค.
์ถ๊ฐ์ ์ผ๋ก atom์ callback ํจ์๋ก get๊ณผ set์ ์ธ์๋ก ๋ฐ์์ ํ์๋ ์ํ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
๋ํ, SSR, ๋น๋๊ธฐ, Family ๋ฑ ๋ค์ํ ์ ํธ๋ฆฌํฐ๊ฐ ์ง์๋ฉ๋๋ค.
NPM์ ํ์ธํด ๋ณด๋ฉด ๋งค์ฐ ์์ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง๊ณ ์๊ณ ๊พธ์คํ ์ ๋ฐ์ดํธ๋ ์ ๋๊ณ ์์ต๋๋ค.
์ฃผ๊ฐ ๋ค์ด๋ก๋ ์ญ์ Recoil ๋ณด๋ค ์ฝ 30๋ง ํ ์ ๋ ๋์ ๊ฒ์ผ๋ก ํ์ธ๋ฉ๋๋ค.
๋ฒ์ ์ ๋ณด๋ฉด ๋ฒ์จ 2+ ๋ฒ์ ๊น์ง ์ ๋ฐ์ดํธ๊ฐ ๋์๋ค์.
ํ์ง๋ง Jotai ์ญ์ ์ฐธ๊ณ ์๋ฃ๊ฐ ๋ฐฉ๋ํ์ง ์๊ณ , ๊ณต์์ ์ธ DevTools ์ง์์ด ๋์ง ์์ต๋๋ค.
๊ทธ๋ผ ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํด์ผ ํ ๊น?๐ง
๊ฐ์ธ์ ์ธ ๊ฒฌํด๋ก ์ต๊ทผ ๋ํฅ์ ์ดํด๋ดค์ ๋,
Redux โก๏ธ Zustand
Recoil, ContenxtAPI โก๏ธ Jotai
์ ๋๋ก ๋์ฒด๋๊ณ ์๋ ๊ฒ ๊ฐ์ต๋๋ค.
๋์ฒด๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ํน์ง์ ์ดํด๋ณด๋ฉด ๋งค์ฐ ๊ฒฝ๋ํ๋ ํฌ๊ธฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก,
์ต์ํ๋ ์ฝ๋๋ก ๊ธฐ์กด์ ์ฌ๋งํ ๊ธฐ๋ฅ๋ค์ ๋์ฒดํ ์ ์๊ณ ๊พธ์คํ ๊ด๋ฆฌ๋๊ณ ์ ๋ฐ์ดํธ๋๊ธฐ ๋๋ฌธ์ ๋ง์ ๊ด์ฌ์ ๋ฐ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ถ๊ฐ์ ์ผ๋ก Typescript๋ฅผ ์งํฅํ๋ค๋ ํน์ง๋ ์์ด์.
ํ์ง๋ง "์์ฆ ๋์ธ๋ค, ์ ํ์ด๋ค, ์ต์ ๊ธฐ์ ์ด๋ค" ์ด๋ฐ ํ๋ฆ์ ํฉ์ธ๋ฆฌ๋ ๊ฒ์ด ์๋๋ผ ํ๋ก์ ํธ ํน์ฑ์ ๋ง๊ฒ ์ ์ฌ์ฉํด์ผ ํ๋ ๊ฒ์ ๋ถ๋ช ํ๋ค๊ณ ์๊ฐํฉ๋๋ค.
์๋ฅผ ๋ค์ด, ๋๊ท๋ชจ ํ๋ก์ ํธ์์ ๋ง์ ๊ธฐ๋ฅ์ ์๊ตฌํ์ฌ ์ฐธ๊ณ ์๋ฃ๊ฐ ๋ง์์ผ ํ๋ ๊ฒ์ ์ค์ํ๊ฒ ์๊ฐํ๋ฉด Redux๋ฅผ ์ ํํ ์๋ ์๊ณ ์.
๋์ ๋ฐ๋๋ก ์๊ฐํด๋ณด๋ฉด, ์ฌ๋๋ค์๊ฒ ๊ด์ฌ๋ฐ๋ ์ด์ ์ ์ฐ์ํฅ์ธ ์ถ์ธ์๋ ๋ถ๋ช ํ ์ด์ ๊ฐ ์์ต๋๋ค.
๊ฐ์ธ์ ์ผ๋ก๋ ํ์ Recoil์ ์ฌ์ฉํ ์ํฉ์๋ Jotai๋ฅผ ์ ํํ๊ฒ ๋ ๊ฒ ๊ฐ๋ค์.
๋ํ, ํ์ฌ ์ ๋ redux-toolkit์ ์ต์ํ์ง๋ง ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด ์ดํ์ Zustand๋ฅผ ๊ฒฝํํด๋ณด๊ณ ์ถ๋ค์.
๋ง์ง๋ง์ผ๋ก ๊ฐ๋จํ๊ฒ ํ๋ก ๋น๊ตํด๋ณด๊ฒ ์ต๋๋ค.
2024๋ 3์ 25์ผ ๊ธฐ์ค์ ๋๋ค.
๋น๊ต | redux-toolkit | Recoil | Zustand | Jotai |
ํฌ๊ธฐ (Unpacked Size) | 5.51MB | 2.21MB | 327KB | 430KB |
์ฃผ๊ฐ ๋ค์ด๋ก๋ ์ | ์ฝ 330๋ง ํ | ์ฝ 60๋ง ํ | ์ฝ 320๋ง ํ | ์ฝ 90๋ง |
์ ์ง๋ณด์ | ์ข์ | ๋๋ฆผ | ์ข์ | ์ข์ |
๊นํ๋ธ start | 10.3k | 19.4k | 41.5k | 17k |
๋ณต์ก๋ | ์ค๊ฐ | ๋ฎ์ | ๋ฎ์ | ๋ฎ์ |
์ฐธ๊ณ ์๋ฃ | ๋์ | ๋ฎ์ | ์ํธ | ๋ฎ์ |
Provider ํ์ | ํ์ํจ | ํ์ํจ | ํ์์์ | ์ ํ์ |
DevTools ์ง์ | โญ | โ | โญ | โ |
์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ๋ํ ์ธ๋๊ต์ฒด๊ฐ ๋ฐ์ํ๋ ๊ฑธ๊น์?
๋งค์ฐ ํฅ๋ฏธ๋กญ์ต๋๋ค.
์ด๋ด์๋ก ์ ๋ ๊ฐ๋ฐ ํธ์์ฑ, ์์ฐ์ฑ ๋ชจ๋ ์ฆ๊ฐํ๊ธฐ ๋๋ฌธ์ ์ ํ์ง๊ฐ ๋์ด์ง๊ณ ์ข์ ์ ์ฅ์ธ ๊ฒ ๊ฐ์ต๋๋ค :)
๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊น์ด ๋ณด๋ค๋ ๋ํ์ ์ธ ํน์ง์ ๋ํด์๋ง ํ์ด๋ดค์ต๋๋ค.
๋ถ์ ํํ ์ ๋ณด๋, ์ถ๊ฐํ๋ฉด ์ข์ ๋ด์ฉ์ด ์์ผ๋ฉด ๋๊ธ ๋จ๊ฒจ์ฃผ์ธ์. ๐
์ฐธ๊ณ ๋ฌธํ:
- https://careerly.co.kr/comments/76656
- https://kir93.tistory.com/entry/React-%EC%B0%A8%EC%84%B8%EB%8C%80-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-Zustandredux-toolkit-vs-JotaiRecoil
- https://medium.com/@clockclcok/recoil-%EC%9D%B4%EC%A0%9C%EB%8A%94-%EB%96%A0%EB%82%98-%EB%B3%B4%EB%82%BC-%EC%8B%9C%EA%B0%84%EC%9D%B4%EB%8B%A4-ff2c8674cdd5
- https://github.com/reduxjs/redux-toolkit
- https://github.com/facebookexperimental/Recoil
- https://github.com/pmndrs/zustand
- https://github.com/pmndrs/jotai
- https://dev.to/nguyenhongphat0/react-state-management-in-2024-5e7l