npmtrends๋ก ๋ณธ 5๋ ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ค์ด๋ก๋ ์
Zustand์ Redux๋ ๋ ๋ค React์์ ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ง๋ง, ์ค๊ณ ๋ฐฉ์๊ณผ ์ฌ์ฉ์ฑ์ด ๊ฝค ๋ค๋ฅด๋ค.
| ํน์ง | Redux | Zustand |
|---|---|---|
| ์ค์ ๋ณต์ก๋ | โ ๋น๊ต์ ๋ณต์ก (Boilerplate ์ฝ๋ ๋ง์) | ๐ ๊ฐ๋จ (๊ธฐ๋ณธ ํจ์๋ง์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ) |
| Reducer ํ์ ์ฌ๋ถ | โ reducer ํ์ | โ ๋ถํ์ |
| Action & Dispatch ๋ฐฉ์ | โ dispatch(action)์ ํตํด ์ํ ๋ณ๊ฒฝ | ๐ ์ง์ ์ํ ๋ณ๊ฒฝ ๊ฐ๋ฅ (mutate ๋ฐฉ์) |
| Middleware ์ง์ | โ ๊ฐ๋ ฅํ Middleware (Redux Thunk, Saga ๋ฑ) | โ Middleware ๊ฐ๋ฅํ์ง๋ง ๊ธฐ๋ณธ ์ ๊ณต X |
| ๋น๋๊ธฐ ์ฒ๋ฆฌ | ๐ Redux Thunk / Redux Saga ํ์ | ๐ ์ง์ ๋น๋๊ธฐ ํจ์ ์ฌ์ฉ ๊ฐ๋ฅ |
| ๋ฆฌ์กํธ ์ข ์ ์ฌ๋ถ | ๐ React์ ์ข ์์ ์ด์ง ์์ (Vanilla JS์์๋ ์ฌ์ฉ ๊ฐ๋ฅ) | ๐ React ๊ธฐ๋ฐ์ผ๋ก ์ค๊ณ๋จ |
| ์ฌ์ฉ์ ๊ฒฝํ (DX) | ๐ ๋ณด์ผ๋ฌํ๋ ์ดํธ ๋ง์ | ๐ฅ ์ง๊ด์ ์ด๊ณ ๊ฐ๊ฒฐ |
| ๋ฉ๋ชจ๋ฆฌ ์ฑ๋ฅ | โณ ์ฝ๊ฐ ๋ฌด๊ฑฐ์ (Immer + ๊น์ ๋ณต์ฌ ์ฌ์ฉ) | โก ๊ฐ๋ฒผ์ (shallow copy ์ฌ์ฉ) |
Redux๋ Flux ์ํคํ ์ฒ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค.
import { createStore } from 'redux';
// 1. ์ด๊ธฐ ์ํ
const initialState = { count: 0 };
// 2. ๋ฆฌ๋์ ํจ์
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
// 3. ์คํ ์ด ์์ฑ
const store = createStore(counterReducer);
// 4. ์ํ ๋ณ๊ฒฝ
store.dispatch({ type: 'INCREMENT' });
console.log(store.getState()); // { count: 1 }
Redux๋ dispatch(action)์ ํธ์ถํด์ผ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์๊ณ , ์ํ ๋ณ๊ฒฝ์ด ํญ์ ๋ถ๋ณ์ฑ(immutability) ์ ์ ์งํด์ผ ํ๋ค.
Zustand๋ Redux๋ณด๋ค ๋ ์ง๊ด์ ์ด๊ณ ๊ฐ๊ฒฐํ API๋ฅผ ์ ๊ณตํ๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค.
import { create } from 'zustand';
// 1. Zustand ์คํ ์ด ์์ฑ
const useCounterStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 }))
}));
// 2. React ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
function Counter() {
const { count, increase, decrease } = useCounterStore();
return (
<div>
<h1>{count}</h1>
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</div>
);
}
useCounterStore() ํ ์ ์ฌ์ฉํด ์ํ๋ฅผ ์ฝ๊ฒ ๊ฐ์ ธ์ฌ ์ ์๊ณ , set()์ ํธ์ถํ๋ฉด ์ง์ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ค.
โ๏ธ ๋๊ท๋ชจ ํ๋ก์ ํธ์ ์ ํฉ
โ๏ธ ์ํ ๋ณ๊ฒฝ ํ๋ฆ์ด ๋ช
ํํ์ง๋ง, Boilerplate ์ฝ๋๊ฐ ๋ง์
โ๏ธ Middleware, DevTools ์ง์์ด ๊ฐ๋ ฅ
โ๏ธ ์๊ท๋ชจ/์ค๊ท๋ชจ ํ๋ก์ ํธ์ ์ ํฉ
โ๏ธ ๊ฐ๋ณ๊ณ ๊ฐ๊ฒฐํ๋ฉฐ, Hook ๊ธฐ๋ฐ์ด๋ผ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌ
โ๏ธ ์ํ ๋ณ๊ฒฝ์ด Redux๋ณด๋ค ์ง๊ด์ (์ง์ set() ํธ์ถ ๊ฐ๋ฅ)
React์์ Redux๋ Zustand ๊ฐ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋์ค๊ธฐ ์ ์๋ ๋ณดํต Prop Drilling ๋๋ Context API๋ฅผ ์ฌ์ฉํ๋ค.
๋ถ๋ชจ โ ์์ โ ์์ ์ปดํฌ๋ํธ๋ก props๋ฅผ ๊ณ์ ์ ๋ฌํ๋ ๋ฐฉ์.
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} setCount={setCount} />;
}
function Child({ count, setCount }) {
return <GrandChild count={count} setCount={setCount} />;
}
function GrandChild({ count, setCount }) {
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
React์ createContext๋ฅผ ํ์ฉํด ์ ์ญ ์ํ๋ฅผ ์ ๊ณตํ๋ ๋ฐฉ์.
const CounterContext = createContext();
function CounterProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
}
function GrandChild() {
const { count, setCount } = useContext(CounterContext);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
function App() {
return (
<CounterProvider>
<GrandChild />
</CounterProvider>
);
}
๐ โ๋๊ท๋ชจ ํ๋ก์ ํธ, ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ด ํ์ํ๋ฉด Reduxโ
๐ โ๊ฐ๋จํ๊ณ ๊ฐ๋ณ๊ฒ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ค๋ฉด Zustandโ ๐