Redux vs Recoil
Table of Contents
๐คท TL;DR
- Redux๋ ์์ ์ ์ด์ง๋ง, ์ ์ธ๊ณผ ์ฌ์ฉ์ด ๋ณต์กํ๋ค.
- Recoil์ ์ฌ์ฉ์ด ๋งค์ฐ ์ฝ์ง๋ง ์ ์ ๋ฒ์ ์ด ์๋ค.
- ํ๋ก์ ํธ ๊ท๋ชจ์ ๋ฐ๋ผ ๋ง์์ ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํ์
๐ช ์๋ก
์ฐ๋ฆฌ๋ React๋ฅผ ์ฌ์ฉํ ๋ ๋ณดํต useState
๋ฅผ ํตํด ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. useState
๋ ์ ๋ง ์น์ํ์ง๋ง, ํ ๊ฐ์ง ๋๋ฌด ํฐ ๋จ์ ์ด ์์ต๋๋ค. ๋ฐ๋ก ์ปดํฌ๋ํธ๋ผ๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐ ์ฌ์ฉํ๊ธฐ๊ฐ ํ๋ค๋ค๋ ์ ์
๋๋ค. ๊ทธ ์ผ์ ํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด ๋ฐ๋ก ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค. ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ์์ฑ์ ๋ํด ๊ณต๊ฐํ์ง ๋ชปํ๋ ๋ถ๋ค์ ์ํด ์งง์ ํ ๋ง๊ธ์ ๋ง๋ จํด ๋ณด์์ต๋๋ค.
โ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ ์ฌ์ฉํ๋์?
์ ์ ๊ฐ ๋ก๊ทธ์ธํด ์๋ ์ ๋ณด๋ฅผ ๋ด์ state๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๋ฉด Root ์ปดํฌ๋ํธ๋ฅผ ์๋์ ๊ฐ์ด ์์ฑํ ์ ์์ต๋๋ค.
export default function App() {
const [userInfo, setUserInfo] = useState("not logged in");
return (
<React.StrictMode>
<Router>
<Routes>
<Route path="/login" element={<Login setUserInfo={setUserInfo} />} />
</Routes>
</Router>
</React.StrictMode>
);
}
์ฌ๊ธฐ๊น์ง ๊ทธ๋ ๊ฒ ์ด๋ ต์ง ์๋ค์. ๋จ์ํ ๋ก๊ทธ์ธ ํ์ด์ง์ setState ํจ์๋ฅผ ๋น๋ ค์ค์ userInfo๋ฅผ ๊ฐฑ์ ํ ์ ์๊ฒ ํ๋ฉด ๋๋๊น์.
ํ์ง๋ง ๋ก๊ทธ์ธ ํ์ด์ง๊ฐ ์ฌ๋ฌ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌ๋๋ค๋ฉด ์ด๋จ๊น์? ์ด๋ฅผํ ๋ฉด, ๋ก๊ทธ์ธ ํ๋ฉด์์ ๋ด ์๋น์ค์ ๋ํ ๊ด๊ณ ์นดํผ์ ์งง์ ์์๊ฐ์ ๋ฏธ๋์ด๋ฅผ ์ฒจ๋ถํ ์์ฃผ ์ด์ ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋ง๋ค๊ณ ์ถ์ต๋๋ค. ๊ทธ๋ผ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ๋ฅผ ์ํด ์ค์ ๋ก๊ทธ์ธ์ ๊ธฐ๋ฅ์ ํ๋ ๋ถ๋ถ์ ๋ณ๋์ ํ์ ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํด์ผ ํ ๊ฒ ๊ฐ์ต๋๋ค.
export default function Login({
setUserInfo,
}: {
setUserInfo: React.Dispatch<React.SetStateAction<string>>,
}) {
return (
<div>
<div>์ฐ๋ฆฌ ์๋น์ค ๋ฆฌ์ผ๊ตฟ ๋น์ฅ๋ฐ๋ก์ง๊ธ ๋ก๊ทธ์ธ ๋ผ์ํธ์ฐ~!~!</div>
<LoginForm setUserInfo={setUserInfo} />
</div>
);
}
๋ฐ์ setUserInfo
๋ฅผ LoginForm
์ปดํฌ๋ํธ์ ํ๋ฒ ๋ ๋ด๋ ค์ฃผ์์ต๋๋ค. ์.. ์์ง๊น์ง๋ ์ฐธ์ ๋ง ํ๋ค์. ํ์ง๋ง ๋ ํฐ ๋ฌธ์ ๊ฐ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ต๋๋ค. ์ด์ ๋ก๊ทธ์ธํ ์ ๋ณด๋ฅผ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ค๊ณ ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ๋ชจ๋ ๋ผ์ฐํฐ์ ๋ํด ๋ฃจํธ ์ปดํฌ๋ํธ๊ฐ ๊ฐ์ง userInfo
๋ฅผ ์ ๋ฌํด ์ฃผ๊ฒ ์ต๋๋ค.
export default function App() {
const [userInfo, setUserInfo] = useState("not logged in");
return (
<React.StrictMode>
<Router>
<Routes>
<Route path="/login" element={<Login setUserInfo={setUserInfo} />} />
<Route path="/service1" element={<Service1 userInfo={userInfo} />} />
<Route path="/service2" element={<Service2 userInfo={userInfo} />} />
<Route path="/service3" element={<Service3 userInfo={userInfo} />} />
<Route path="/service4" element={<Service4 userInfo={userInfo} />} />
<Route path="/service5" element={<Service5 userInfo={userInfo} />} />
<Route path="/service6" element={<Service6 userInfo={userInfo} />} />
<Route path="/service7" element={<Service7 userInfo={userInfo} />} />
<Route path="/service8" element={<Service8 userInfo={userInfo} />} />
</Routes>
</Router>
</React.StrictMode>
);
}
๋ผ์ฐํฐ๋๊น ์ด์ ๋ ๋๋ฌ์์ ๊ด์ฐฎ์ ๊ฒ ๊ฐ๋ค๊ตฌ์? ๊ทธ๋ ๋ค๋ฉด ์ฌ๋ฌ ์ปดํฌ๋ํธ์์ ํ์๋ก ํ๋ ์ ๋ณด๊ฐ ๋ก๊ทธ์ธ ์ ๋ณด๋ง์ด ์๋๋ผ๋ฉด์? newInfo
๋ฅผ ๋ถํน์ ๋ค์์ ์ปดํฌ๋ํธ๊ฐ ์ฐธ์กฐํ๋ค๋ฉด์?
export default function App() {
const [userInfo, setUserInfo] = useState("not logged in");
const [newInfo, setNewInfo] = useState(0);
return (
<React.StrictMode>
<Router>
<Routes>
<Route path="/login" element={<Login setUserInfo={setUserInfo} />} />
<Route path="/service1" element={<Service1 userInfo={userInfo} />} />
<Route
path="/service2"
element={<Service2 userInfo={userInfo} newInfo={newInfo} />}
/>
<Route path="/service3" element={<Service3 userInfo={userInfo} />} />
<Route path="/service4" element={<Service4 userInfo={userInfo} />} />
<Route
path="/service5"
element={<Service5 userInfo={userInfo} newInfo={newInfo} />}
/>
<Route path="/service6" element={<Service6 userInfo={userInfo} />} />
<Route
path="/service7"
element={<Service7 userInfo={userInfo} newInfo={newInfo} />}
/>
<Route path="/service8" element={<Service8 userInfo={userInfo} />} />
</Routes>
</Router>
</React.StrictMode>
);
}
์์ง๋ ๋ฒํธ ๋ง ํ์ ๊ฐ์? ๊ทธ๋ผ ๋ง์ง๋ง์ผ๋ก **Service3
์ ํ์ ์ปดํฌ๋ํธ์ธ ChildComponent1
์์ ๋ณ๊ฒฝํ ๋ฐ์ดํฐ ๊ฐ์ Service1
์ ํ์ ์ปดํฌ๋ํธ์ธ ChildComponent2
๋ก ์ ๋ฌํด ์ฃผ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?**
๊ทธ๋์ ์ฐ๋ฆฌ ํ์ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด๋ค ๊ฒ์ ์ฌ์ฉํ ์ง ๊ณ ๋ฏผํ์ต๋๋ค. ๋น์ฅ ํ์์์ ๊ฑฐ๋ก ๋์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Redux, Recoil, Context API, Mobx ์ ๋๊ฐ ์๋๋ฐ์, ์ค๋์ ๊ทธ ์ค์ Redux์ Recoil์ ๋ํด ๊ฐ๋ณ๊ฒ ์์๋ณด๊ณ ๋น๊ตํด ๋ณด๋ ์๊ฐ์ ๊ฐ์ก์ต๋๋ค. ์ด ๊ธ์์๋ ์๋ ์๋ฆฌ๋ณด๋ค๋ ์ฌ์ฉ์์ ์ฅ๋จ์ ์ ์ค์ฌ์ผ๋ก ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
๐ฌ Redux
Redux์ ๋ํด.araboja
Redux๋ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์์๋ ๊ฐ์ฅ ๋์ค์ ์ธ ์น๊ตฌ์ ๋๋ค. ์๋ง recoil์ฒ๋ผ ๋ฆฌ์กํธ์ ์ข ์์ ์ด์ง๋ ์๊ณ , mobx๋ณด๋ค ์ค๋๋์๊ธฐ ๋๋ฌธ์ด ์๋๊น์? ์๋ฌดํผ npm trends์์ ํ์ธํ ์ ์๋ ๊ฒ์ฒ๋ผ ์๋์ ์ธ ์ปค๋ฎค๋ํฐ ํฌ๊ธฐ๋ฅผ ์๋ํฉ๋๋ค. ๋ ํผ๋ฐ์ค๊ฐ ๋ฌด์ฒ ๋ง๊ณ , ์.. ๋ ํผ๋ฐ์ค๊ฐ ๋ฌด์ฒ ๋ง์ต๋๋ค. ํ๊ธ๋ก ๊ฒ์ํด๋ ๋น์ฅ ์ด ๊ธ์ ์ธ ์ด์ ๊ฐ ์๋ ์ถ์ ์ ๋๋ก ์ง๋์น๊ฒ ๋ง์ ์ ๋ฆฌ๊ธ์ด ๋์ค๋ค์.
Redux๋ Flux ์ํคํ ์ณ๋ฅผ ๊ฐ์ ํ ๊ตฌ์กฐ๋ก ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. 1) ์ฝ๊ธฐ ์ ์ฉ์ธ ์ํ๋ฅผ 2) ํ๋์ store๊ฐ 3) ์์ํจ์๋ฅผ ์ด์ฉํด ๊ฐฑ์ ํ๋ค๋ ์ธ ๊ฐ์ง ๋ฉ์ธ ์ปจ์ ์ด ์๋ค๊ณ ํ๋ค์. ๋ ์์ธํ ์ค๋ช ์ ์ ์ฌ์ง์ ์ถ์ฒ ๋งํฌ์ ์ ์ค๋ช ์ด ๋์ด ์์ต๋๋ค.
์ฌ์ฉํด ๋ด ์๋ค
npm i redux react-redux @reduxjs/toolkit
์ฐ์ ์ธ ๊ฐ์ง ํจํค์ง๋ฅผ ์ค์นํ๊ฒ ์ต๋๋ค. @reduxjs/toolkit
์ด๋ผ๋ ํจํค์ง๋ redux๋ฅผ ์กฐ๊ธ ๋ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ ๋๊ตฌ์ธ๋ฐ์, ๊ฐ์ธ์ ์ผ๋ก RTK ์์ด ๋ฆฌ๋์ค๋ฅผ ์ฒ์ ์์ํ๊ธฐ๊ฐ ๊ต์ฅํ ์ด๋ ค์ ๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ๋ ์ชฝ์ผ๋ก ๊ธธ์ ํ์์ต๋๋ค.
// count.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface CounterState {
// State์ interface ์ ์ธ
value: number;
amount: number;
}
const initialState: CounterState = {
// ์ด๊ธฐ๊ฐ ์ ์ธ
value: 0,
amount: 1,
};
export const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increase: (state) => {
state.value += state.amount;
},
decrease: (state) => {
state.value -= state.amount;
},
init: (state, action: PayloadAction<number>) => {
// value๊ฐ์ ์ง์ ์ค์ ํ๋ action
state.value = action.payload;
},
},
});
export const { increase, decrease, init } = counterSlice.actions;
export default counterSlice.reducer;
์ ์ญ ์ํ๋ง๋ค ์์ ๊ฐ์ด reducer๋ฅผ ์ ์ธํด์ฃผ๋ฉด ๋ฉ๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ์๋ init
action์ฒ๋ผ setState
์ ์ญํ ์ ํ๋ ๋
์๋ง ์ ์ธํด ์ฃผ๋ฉด ๋ฌธ์ ์์ด ์ฌ์ฉํ ์ ์์ ๊ฒ์ผ๋ก ๋ณด์ด๋ค์. ๊ทธ๋ผ์๋ ์ฌ๋ฌ ๊ฐ์ action์ ์ ์ธํ ์ ์๋ค๋ ๋ถ๋ถ์ ๋งค๋ ฅ์ ์ผ๋ก ๋ณด์
๋๋ค. ๋ง์ฝ ์ ์ญ ์ํ๊ฐ Object์ฒ๋ผ ๋ณต์กํ ํ์
์ผ๋ก ๋์ด ์๋ค๋ฉด ๊ด๋ฆฌํ๋ ๋ก์ง์ action์ ์ ์ธํด ๋๊ณ ์ฌ์ฌ์ฉํ๋ฉด ๋๋๊น์.
๐ก ESLint airbnb rule๊ณผ์ ์ถฉ๋
createSlice๋ก ๋ฆฌ๋์๋ฅผ ๋ง๋ค๋ค ๋ณด๋ฉด ์๋ ์ฌ์ง๊ณผ ๊ฐ์ด ํจ๋ฌ๋ฏธํฐ๋ก ๋ค์ด์จ state๊ฐ ๊ฐ์ง ๊ฐ์ ์กฐ์ํด์ ์๋๋ค๋ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ๋ง์ฃผ์น๊ฒ ๋ฉ๋๋ค. airbnb ๋ฃฐ์ ํฌํจ๋์ด ์๋ ๊ท์น์ธ๋ฐ, redux-toolkit Issue #521์์ ์ด์ ๋ํ ์ค๋ช ์ ํ์ธํ ์ ์์์ต๋๋ค. ์ฌ๊ธฐ ์ฌ๋๋ค์ airbnb ๋ฃฐ์ ๊ทธ๋ฅ ์ข์ํ์ง ์๋๊ตฐ์!
ํด๋น Rule์ ๋นํ์ฑํ ํ๋ ๊ฒ ์ธ์ ์ฝ๊ฐ์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ ๋ฐฉ๋ฒ๋ ์๋๋ฐ์, ์ด ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์๋ฉด ๋๊ฒ ์ต๋๋ค.
// store.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./count";
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
๋ง๋ reducer๋ค์ ๊ด๋ฆฌํ๋ store
๋ฅผ ์ ์ธํฉ๋๋ค. ํํ ์๊ณ ๊ณ์๋ store์ ๊ฐ๋
๊ณผ ๊ฐ์ผ๋ฉฐ, ์ ์ญ ์ํ๊ฐ ๋ ํ์ํ ๋ ๋ง๋ค ๋ง๋ reducer๋ค์ configureStore
์์ ์ถ๊ฐํด์ฃผ๋ฉด ๋๊ฒ ์ต๋๋ค. ์ฌ์ฉํ ์ ์ญ ์ํ ์๋งํผ importํด์ ์ฌ์ฉํ๋ค๋ ์๊ฐ์ผ๋ก ์์ฑํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
// App.tsx
import React, { useState } from "react";
import { Provider } from "react-redux";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.scss";
import Login from "./Login";
import { store } from "./redux/store";
import Test from "./Test";
export default function App() {
return (
<React.StrictMode>
<Provider store={store}>
<Router>
<Routes>
<Route path="/" element={<Test />} />
<Route path="/login" element={<Login />} />
</Routes>
</Router>
</Provider>
</React.StrictMode>
);
}
์ด์ ๋ง๋ store๋ฅผ Provider
๋ฅผ ํตํด ์ฐ๊ฒฐํด ์ค๋๋ค. Router
์ ๋น์ทํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ฐ, Provider
๋ก ๋ฌถ์ฌ ์๋ ์ปดํฌ๋ํธ๋ค์ ๋ชจ๋ store์ ์ํ๋ค์ ์ฐธ์กฐํ๊ณ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค. ์ํ๋ฅผ ์ฌ์ฉํ ์ ์๋ scope๋ฅผ ์ง์ ํ๋ ๋๋์ผ๋ก ์ดํดํ๋ฉด ๋๊ฒ ๋ค์.
// Test.tsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { decrease, increase, init } from './redux/count';
import { RootState } from './redux/store';
export default function Test() {
const count = useSelector((state: RootState) => state.counter.value);
const dispatch = useDispatch();
return (
...
<button type="button"
onClick={() => {
dispatch(decrease());
}}
>-</button>
...
);
}
์ค์ ์ฌ์ฉ์ ์์ ๊ฐ์ด ํฉ๋๋ค. ์ฐธ์กฐํ ์ํ๋ useSelector
๋ก, action์ useDispatch
๋ฅผ ์ด์ฉํด ๋ฐ์์ํต๋๋ค. ๊ฐ์ธ์ ์ผ๋ก๋ action ๋ฉ์๋๋ฅผ importํด์๋๋ฐ ๊ทธ๊ฑธ ๋ฐ๋ก ์ฌ์ฉํ์ง ๋ชปํ๊ณ useDispatch
๋ฅผ ํตํด ์ฌ์ฉํด์ผ ํ๋ค๋ ์ ์ด ์กฐ๊ธ ์ด์ํ๊ฒ ๋๊ปด์ก์ต๋๋ค. ๋ ํ์
์ค๋ฅ๊ฐ ์์๋๋ฐ, ์ด๋ฅผ ์ํด RootState
๋ผ๋ ๋ฐํ ํ์
์ ์ ์ธํด์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. store.ts
์ฝ๋์ ๋ณด์ด๋ ๊ฒ์ฒ๋ผ ์๋ฌด๋ฐ๋ ์ฌ์ฉํด๋ ๋ ๋งํผ ์ถ์ํ๊ฐ ๋์ด์๋ ํ์
์ธ๋ฐ ๊ธฐ๋ณธ์ผ๋ก ์ง์ํด์ฃผ์ง ์๋ ๊ฒ์ ์กฐ๊ธ ์์ฝ์ต๋๋ค.
์์ฃผ ๋ฌด์ฉ๊ธด ์ปดํฌ๋ํธ๋ฅผ ํตํด ์ ๋๋ก ์๋ํจ์ ํ์ธํ ์ ์์์ต๋๋ค.
๊ทธ๋์ Redux ์ฐ๋์?
์ฅ์
- ๋ ํผ๋ฐ์ค๊ฐ ๋งค์ฐ ๋ง์ต๋๋ค.
- ๊ธ์๋ ์ธ๊ธํ์ง ์์์ง๋ง Redux Devtools๋ฅผ ํตํด ๋๋ฒ๊น ์ ์ง์ํด ์ค๋ค๊ณ ํฉ๋๋ค.
- (Recoil๊ณผ ๋น๊ตํด์) ์์ ์ ์ด๊ณ , ๋ฏฟ์ ์ ์์ต๋๋ค. ์ ๋ง ์ ์ฌ์ฉํ๋ค๋ฉด์.
๋จ์
- reducer, store, type ๋ฑ๋ฑ ์ํ ํ๋๋ฅผ ์ถ๊ฐํ๊ธฐ ์ํ ์ฝ๋๊ฐ ์ ๋ง ๊น๋๋ค.
- ๋ ํผ๋ฐ์ค๊ฐ ๋๋ฌด ๋ง์ต๋๋ค.
- ์์ ๋ ๋ฌธ์ ์ ์ผ๋ก ์ธํด ์ง์ ์ฅ๋ฒฝ์ด ๊ต์ฅํ ๋์์ต๋๋ค.
Redux์ ๋ํ ์ฒซ ์ธ์์ ๊ต์ฅํ ๋ถ์น์ ํ์ต๋๋ค. ์ปค๋ฎค๋ํฐ๊ฐ ํฐ ๊ฒ์ ์ข์๋ฐ, ์ง์ง ๋๋ฌด ๋๋ฐ ํฌ๋ค๋ณด๋ ๋ ํผ๋ฐ์ค๋ค์ด ์๋ก ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก ์ ๋ฅผ ์ด๋๋ ๊ฒฝ์ฐ๊ฐ ๊ฝค ๋ง์์ต๋๋ค. ์ค์ ๋ก ์ ๋ ๋ํ๊ต ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ์ด๋ฐ ์ ๋๋ฌธ์ Redux๋ฅผ ํฌ๊ธฐํ๊ณ props๋ฅผ ๋ง๊ตฌ ๋ด๋ ธ๋ค ์ฌ๋ ธ๋ค ํ๋ ์์์ ์ธ ๋ฐฉ๋ฒ์ ์ ํํ๊ธฐ๋ ํ๊ตฌ์.
๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ Redux๋ฅผ ์ฌ์ฉํ ์ค ์๋ ๊ฒ์ ์ค์ํฉ๋๋ค. ์ปค๋ฎค๋ํฐ๊ฐ ํฌ๋๊น์. ๊ธฐํ๊ฐ ๋ ๋๋ง๋ค ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ์ง๋ฌธ์ ํด ๋ณด๋ฉด, ํฐ ํ๋ก์ ํธ์์๋ ์์ง Redux๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค๋ ๋ต๋ณ์ ๋ฃ๊ณค ํฉ๋๋ค. ๋ ์ฃผ๋ณ์ Redux๋งจ๋ค์๊ฒ ๋ฌผ์ด๋ณด๋ฉด ๋ช ๋ฒ ๊ฒช์ด๋ณด๋ฉด ๊ธ๋ฐฉ ์ต์ํด์ง๋ค๊ณ ํ๋, ์ฒซ ๊ณ ๋น๋ฅผ ๋๊ธฐ๊ณ ๋๋ฉด ์ ์ฌ์ฉํ ์ ์์ง ์์๊น.. ๊ทธ๋ฐ ๊ธฐ๋๋ฅผ ํด ๋ด ๋๋ค.
๐ Recoil
๋ฆฌ-ํ(๋ฆฌ์ฝ์ผ ํ์ด๋ผ๋ ๋ป)
์ด๋ฒ์ Recoil์ ์ฒดํํด ๋ณผ ์ฐจ๋ก์ ๋๋ค. Recoil๋ก ๋งํ ๊ฒ ๊ฐ์ผ๋ฉด ๋ฆฌ์กํธ๋ฅผ ๋ง๋ ํ์ด์ค๋ถ์ด ์ง์ ๊ณต๊ฐํ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ฐ๋ฆฌ ๋ชจ๋๊ฐ React๊ฐ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋์ํ๋ค๋ ๊ฒ์ ์๊ณ ์์ง๋ง, ๋ฆฌ์กํธ์ ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ์กฐ๊ธ ๋ค๋ฅธ ์ธ๊ณ์ฒ๋ผ ๋๊ปด์ง๋ ๋ถ๋ค์ด ์์ ๊ฒ์ ๋๋ค. ์ด๋ฅผํ ๋ฉด, React๋ฅผ ์ํด ๋ง๋ค์ด์ง ํจํค์ง๋ React์์ ์ฌ์ฉํ๊ณ , VanilaJS๋ฅผ ์ํด ๋ง๋ค์ด์ง ํจํค์ง๋ VanilaJS์์๋ฐ์ ์ธ ์ ์๋ ๊ฒ์ฒ๋ผ ์๊ฐํ๋ ๋ถ๋ค์ด ์์ฃ .
์ด๋ฐ ๊ฑฐ๋ฆฌ๊ฐ๋๋ฌธ์ ๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ง์ โReact์ค๋ฌ์ดโ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๊ธฐ๋ ํฉ๋๋ค. ์ ์ฌ์ง์ ์ ๋ฒ ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ ์บ๋ฒ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ konva
์ ๋ฆฌ์กํธ ๋ฒ์ , react-konva
์ฝ๋ ์ผ๋ถ์
๋๋ค.
์ ๊ฐ ์ด ์ด์ผ๊ธฐ๋ฅผ ์ ํ๋ ๊ฒ์ผ๊น์? Recoil
์, ์ค์ง React๋ง์ ์ํด ๋ง๋ค์ด์ง React ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด ์ ์ ๊ฐ์ฅ ํฐ ํน์ง์ผ๋ก ๋ง์๋๋ฆด ์ ์๊ฒ ๋ค์. ๊ทธ ์ธ์๋ atom ๊ตฌ์กฐ๋ฅผ ํตํด์ ์ํ๋ฅผ ๊ด๋ฆฌ ์ด์ฉ๊ตฌ.. ํ๋ ์๋ ๋ฐฉ์์ ๋ํ ๋ด์ฉ์ด ์์ต๋๋ค. ์ด ๊ธ์์ ํ์ง ์๊ธฐ๋ก ํ ์ด์ผ๊ธฐ๋ค์.
์ฌ์ฉํด ๋ด ์๋ค
npm i recoil
์ค์นํด ์ค๋๋ค. redux์ ๋น๊ตํ์ ๋ ๋ณ๋ค๋ฅธ ์ค์น๋ฅผ ์๊ตฌํ์ง ์๊ธด ํ๋ฐ, ํ .. ๋ณ ์๊ฐ์ ๋ค์ง ์์ต๋๋ค.
// atom.ts
import { atom } from "recoil";
const counterState = atom({
key: "count",
default: 0,
});
export default counterState;
redux์ reducer์ ๋น์ทํ ์ญํ ์ ํ๋ atom
์ ์ ์ธํด ์ค๋๋ค. reducer๋ ์ด๊ธฐ ๊ฐ๊ณผ action๊น์ง ๋ชจ๋ ์ ์ํ์ง๋ง, atom
์ key
์ default value
๋ง ์ง์ ํด์ exportํด์ฃผ๋ฉด ๋ฉ๋๋ค.
// App.tsx
...
export default function App() {
return (
<React.StrictMode>
<RecoilRoot>
<Router>
<Routes>
<Route path="/" element={<Test />} />
<Route path="/login" element={<Login />} />
</Routes>
</Router>
</RecoilRoot>
</React.StrictMode>
);
}
redux์ Provider์ฒ๋ผ ์ญ์ RecoilRoot
๋ฅผ ํตํด atom์ ์ฌ์ฉํ ์์ญ์ ๋ฌถ์ด ์ค๋๋ค. ๋ณ๋ค๋ฅธ store๋ฅผ ์ง์ ํ์ง ์๋๋ก ๋์ด ์๋๋ฐ, ๊ฐ๊ฐ์ RecoilRoot
๋ ๋ชจ๋ atom์ ์ฌ์ฉํ ์ ์๋ ๋ชจ์์
๋๋ค. ํ์ง๋ง ์ฌ๋ฌ RecoilRoot
๋ ์๋ก atom ๊ฐ์ ๊ณต์ ํ์ง ์๊ธฐ ๋๋ฌธ์, ์ฌ๋ฌ ๊ฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ด๋ ์์ญ์ ์๋์ง๋ฅผ ์ ์ฒดํฌํ ํ์๊ฐ ์๊ฒ ๋ค์. ๊ผญ ๋ฉํฐ๋ฒ์ค..๊ฐ์ต๋๋ค.
// TestRecoil.tsx
import React from 'react';
import { useRecoilState } from 'recoil';
import counterState from './recoil/atom';
export default function Test() {
const [count, setCount] = useRecoilState(counterState);
return (
...
<button type="button"
onClick={() => {
setCount(count - 1);
}}
>-</button>
...
);
}
Recoil์ useState
ํ
๊ณผ ๊ฐ์ ์ฌ์ฉ๋ฒ์ ๊ฐ์ต๋๋ค. import๋ง ์ ํด์คฌ๋ค๋ฉด, useRecoilState
๋ฅผ ํตํด atom์ ์ฌ์ฉํ๊ฒ ๋ค ์ ์ธํ๊ณ ๋ฐ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. Recoil์ ๊ฐ์ ์ด ๋๋ฌ๋๋ ๋ถ๋ถ์ด๋ค์. ์ฐธ์กฐ๋ง ํ๋ ๊ฒฝ์ฐ์๋ useRecoilValue
, ํ ๋น๋ง ํ๋ ๊ฒฝ์ฐ์๋ useSetRecoilState
๋ฅผ ํตํด ํ ์ชฝ๋ง ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
๊ฒฐ๊ณผ๋ฌผ์ Redux์ ๋๊ฐ์ด ๋์ํฉ๋๋ค.
๊ทธ๋์ Recoil ์ฐ๋์?
์ฅ์
- React์ค๋ฌ์
- atom์ ์ ์ธ์ด ๊ฐ๋จํ๋ค
๋จ์
- ์์ง ์คํ์ ๊ธฐ๋ฅ(experimental feature)์ด๋ค
- ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฌธ์
- React์ ์ข ์์ ์ด๋ค.
๊ทธ๋ ์ต๋๋ค. ์์ฝ๋ง ๋ด๋ ๋๊ปด์ง์ง๋ง Recoil์ ๊ทธ๋ ๊ฒ ์์ ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋๋๋ค. ์ฐ์ ๋ ํฌ์งํ ๋ฆฌ ์์ฒด๊ฐ facebookexperimental์ ์๊ณ , issue์ ๊ฒ์ํด๋ณด๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์์ ๋ํ ๋ ํฌํธ๊ฐ ๊ฝค ๋ง์ต๋๋ค. ์ด๋ฐ ๋ฌธ์ ๋๋ฌธ์ Recoil์ ์์ง๊น์ง๋ โ๋ฆฌ์กํธ์ค๋ฝ๋คโ๋ ์๋์ ์ธ ๊ฐ์ ์ ๊ฐ์ง๊ณ ๋ ๊ทธ๋ ๊ฒ ์ข์ ์ฑ์ ์ ๋ด์ง ๋ชปํ๊ณ ์์ต๋๋ค. ์ค์ ์๋น์ค๋ฅผ ๊ตฌํํ๋๋ฐ โ์คํ์ ๊ธฐ๋ฅโ์ ์ํ ๊ด๋ฆฌ๋ฅผ ๋ฏฟ๊ณ ๋งก๊ธธ ์ ์์๊น์? React๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค ๋ณด๋ React ์ํ๊ณ์์ ๋ฒ์ด๋๋ค๋ฉด ๋ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณต๋ถํด์ผ ํ๋ค๋ ์ ๋ ๋ฌธ์ ๊ฐ ๋ ๊ฒ ๊ฐ๋ค์. ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฌธ์ ์ ๋ํด ๋ ์์ธํ ์๊ณ ์ถ์ผ์๋ฉด ์ด ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
ํ์ง๋ง Recoil์ ๋ถ๋ช ๋งค๋ ฅ์ ์ ๋๋ค. ํนํ React๋ฅผ ํตํด ๊ตฌํํ๋ค๋ฉด Recoil์ ๋งค๋ ฅ์ ๋ฟ๋ฆฌ์น๊ธฐ๋ ์ฝ์ง ์์ต๋๋ค. ์๋๋ฉด Recoil์ React์ค๋ฌ์ฐ๋๊น์. useState๋ฅผ ์ฐ๋ ๊ฒ๊ณผ ์์ ๋๊ฐ์ด ์ ์ญ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ค๋, ์ด ํน์ฑ ํ๋๋ง์ผ๋ก๋ ์ ํํ ์ด์ ๋ ์ถฉ๋ถํด ๋ณด์ ๋๋ค.
โ ๊ทธ๋์ ๋ญ ์ฐ๋์?
Redux์ Recoil์ ๋ํด ์์๋ดค์ต๋๋ค. ์ฌ์ค ์ฐ๋ฆฌ ํ๋ก์ ํธ์ ์ ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํ๊ธฐ ์ํด ๊ณต๋ถํ๋๋ฐ, ๊ณต๋ถํ๊ณ ๋๋ ๋ ๋ณต์กํด์ง๋ค์. ํ์ง๋ง ์ด๋ ํ ์ชฝ์ด โ์ข๋คโ๊ณ ๊ฒฐ๋ก ์ง์ ์ ์๋ ๋ฌธ์ ๋ ์๋๊ณ ํฅ๋ถ์ ๋จน์์ง ์๋ถ์ ๋จน์์ง, ๊ทธ ๋ ์ ๊ธฐ๋ถ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ ๊ทธ๋ฐ ์ ํ์ผ ๊ฒ ๊ฐ์ต๋๋ค. ์ฐธ๊ณ ๋ก ์ ๋ ํฅ์ ์ซ์ดํฉ๋๋ค.
์์ผ๋ก์ ํ๋ก์ ํธ์์ ์ ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅธ๋ค๋ฉด ์ด๋ค ๊ธฐ์ค์ผ๋ก ์ ํ ์ ์์๊น์? ์ ๋ ํ๋ก์ ํธ์ ๊ท๋ชจ๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒฐ์ ํ ๊ฒ ๊ฐ์ต๋๋ค. ์ฌ์ค Redux๋ ์์ ์ ์ด์ง๋ง ํ ์ด ํ๋ก์ ํธ์ ์ ์ฉํ๊ธฐ์๋ ๋๋ฌด ๋ง์ ๋ฐ์์ ์ ํ์๋ก ํ๊ฑฐ๋ ์. ๋ฐ๋ฉด ์ปค๋ค๋ ํ๋ก์ ํธ๋ฅผ ํด์ผ ํ๋ค๋ฉด Redux๋ฅผ ์ฌ์ฉํด์ผ ํ ๋ ์ด ์ฌ ์๋ ์๊ฒ ์ฃ . ๋ง๋ถ์ด์๋ฉด ์ปดํผ๋๋ฐ์ด ๋ ์ด๋ค ๊ธฐ์ ์์๋ Redux์ Recoil์ ํจ๊ป ์ฌ์ฉํ๋ค๋ ๋ต๋ณ์ ์ฃผ์๊ธฐ๋ ํ์ต๋๋ค.
๐ Refs.
TypeScript and redux tool kit , createSlice: Assignment to property of function parameter ‘state’
Getting Started with Redux
๋งค๊ฐ๋ณ์ ์ฌํ ๋น์ ์ง์ํ์(no-param-reassign)
Redux Toolkit with Typescript: How to Get Started
RecoilRoot | Recoil
React์ค๋ฌ์ด ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ, Recoil์ ์์๋ณด์
Recoil์ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฌธ์
๊ทธ๋ฆฌ๊ณ ๋ถ์คํธ์บ ํ ์ปจํผ๋ฐ์ค๋ฅผ ํตํด Recoil์ ๋ํด ์ค๋ช
ํด ์ฃผ์ J039 ๊น์ฑ์ ์บ ํผ๋๊ป ๊ฐ์ฌ์ ์ธ์ฌ๋ฅผ ์ฌ๋ฆฝ๋๋ค. ๐