대부분의 선배 개발자 분들이 React 에서 Recoil, Redux 같은 state management library 를 사용해야 한다고 하는데 아직 그 이유를 몰라서 그 이유에 대해서 공부 해보겠습니다
그래서 다크모드와 라이트모드를 바꾸는 스위치 기능을 그냥 만들어보고 Recoil 을 이용해 만들어 보면서 왜 recoil, state management 가 필요한지, 기술은 문제를 해결하기 위해 만들어지는 거라는 것을 명심하며 공부해 보겠습니다.
state management 없이 만드려면 다크모드/라이트모드 스위치를 만드려면 두가지를 바꿔야합니다.
우선 이전에 만들어둔 코인시세 트래커 서비스 App.tsx 파일에 useState 를 사용하여 지금 Dark 모드인지 아닌지 상태 파악 할 수 있게 해줍니다.
import { createGlobalStyle, ThemeProvider } from "styled-components";
import Router from "./Router";
import { ReactQueryDevtools } from "react-query/devtools";
import { useState } from "react";
import { darkTheme, lightTheme } from "./theme";
...
function App() {
const [isDark, setIsDark] = useState(false);
return (
<>
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<GlobalStyle />
<Router>
<ReactQueryDevtools initialIsOpen={true} />
</ThemeProvider>
</>
);
}
여기에 const toggleDark 로 function 을 만들고 내부에서 setIsDark 를 사용합니다.
setState function 을 사용할때 두 가지 옵션 중에서 function 보내는 옵션을 사용합니다.
(1. value 를 그냥 보내는 것, 2. function 을 보내는 것)
...
function App() {
const [isDark, setIsDark] = useState(false);
const toggleDark = () => setIsDark(true
return (
...
);
}
그리고 잘 동작하는지 확인하기 위해서 "Toggle Mode" 버튼을 위에 만든 setState function(toggleDark)을 onClick으로 이벤트 펑션으로 받으며 작성합니다.
...
function App() {
const [isDark, setIsDark] = useState(false);
const toggleDark = () => setIsDark(true
return (
...
<button onClick={toggleDark}>Toggle Mode</button>
...
);
}
위에서 다크모드/라이트모드 기능이 구현되는 것을 확인 하였으니 이제 토글의 위치를 수정해주고 메인화면의 Text, Box 들 뿐아니라 Chart 에서도 적용 되도록 Chart 컴포넌트(Chart.tsx) 에서도 Props 를 전달 해줘야 합니다.
토글의 위치를 메인화면 헤더 타이틀 옆으로 수정
<Header>
<Title>Coins</Title>
<button onClick={toggleDark}>Toggle Dark Mode</button>
</Header>
App 컴포넌트에서 만들어놓은 toggleDark function 을 메인화면 컴포넌트(Coins.tsx) 에 전달해줘야 하고 메인화면 컴포넌트는 Router 내부에 있기 때문에 이 경우 function 을 Router 로 보내야 합니다. 그리고 TypeScript 를 사용중이기 때문에 interface 도 생성해줍니다.
import { HashRouter, Switch, Route } from "react-router-dom";
import Coin from "./routes/Coin";
import Coins from "./routes/Coins";
interface IRouterProps {
toggleDark: () => void;
}
function Router({ toggleDark }: IRouterProps) {
return (
<HashRouter>
<Switch>
<Route path="/:coinId">
<Coin />
</Route>
<Route path="/">
<Coins toggleDark={toggleDark} />
</Route>
</Switch>
</HashRouter>
);
}
export default Router;
Router 에서 메인페이지 컴포넌트로 toggleDark 상태판단 함수를 또 보내줍니다. 또 interface 도 동일하게 생성해줍니다.
interface ICoinsProps {
toggleDark: () => void;
}
function Coins({ toggleDark }: ICoinsProps) {
const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins); // hook
return (
<Container>
<Helmet>
<title>Coins</title>
</Helmet>
<Header>
<Title>Coins</Title>
<button onClick={toggleDark}>Toggle Dark Mode</button>
</Header>
현재는 global state management 라이브러리 없이 진행하다보니 props 를 두 단계나 아래로 보내고 있는 상황이 발생했습니다.
그리고 Chart 컴포넌트에 다크모드/라이트모드를 적용하기 위해서 위의 과정을 또 반복해야합니다. 추가로 상태값인 isDark 도 보내줘야합니다.
따라서 위에 했던 과정에서 toggleDark function 만 보내는 것이 아니라 상태값 isDark 도 보낼 수 있도록 추가해주고 isDark 를 Chart 컴포넌트까지 내려준 후 다크모드/라이트모드 변경이 가능하도록 작성합니다.
interface IChartProps {
coinId: string;
isDark: boolean;
}
function Chart({ coinId, isDark }: IChartProps) {
...
return (
...
options={{
theme: {
mode: isDark ? "dark" : "light",
},
이번에는 Chart 컴포넌트까지 세 단계로 Props 를 내려주었고 이는 명백히 효율적이지 않습니다.
그리고 서비스가 커질 수록 관리해야하는 state 들도 많아질 것입니다.
state 가 만들어지고 전달되는 과정을 한 번 정리 해보겠습니다.
App (isDark, modifierFn)
-> Router -> Coins (modifierFn)
-> Router -> Coin -> Chart (isDark)
상당히 긴 여정이였습니다.
이 글에서는 단지 다크모드/라이트모드의 state 만을 가지고 global state 와 왜 state management 라이브러리 가 필요한지 공부해 보았습니다.
실제로는 유저로그인 확인 등 하나의 상태가 변경됨으로 더 많은 것들이 변경되는 기능들이 필요할 것입니다.
따라서 상태관리 코드를 다른 곳에 둔다면, props 를 엄청 내려보내지 않아도 component 가 이들에 언제 접근할지 선택 가능해 질것입니다.
이에 대해서 상태관리 코드를 다른 곳에 두는 것과 현재의 방법의 비교를 해보겠습니다.
// 현방법(without state management 라이브러리)
isDark: App -> Router -> Coin -> Chart
// 상태관리 코드를 다른 곳에 두는 방법(ex_Recoil)
Header -> (isDark) <- Chart
App -> (isDark)
State Management 라이브러리를 사용하는 것이 훨씬 간단해 보입니다.
다음 글에서는 Recoil 을 사용하여 다크모드/라이트모드 구현하는 것을 진행해보겠습니다.