so woon!

[RECOIL] Recoil 시작하기 + Recoil을 사용하여 다크모드 구현하기 본문

Recoil/개념정리

[RECOIL] Recoil 시작하기 + Recoil을 사용하여 다크모드 구현하기

xowoony 2023. 4. 15. 13:55

학습일 : 2023. 04. 15


Recoil을 사용하지 않았던 개발환경에서는
단순한 작업일 경우 유용하게 쓸 수 있었지만
작업의 양이 많아질 경우 prop을 쭉 내려 보내서 사용하는 global state가
다소 비효율적이다.

예를 들어 로그인한 사용자의 경우
많은 컴포넌트에서 다른 모습을 보여주어야 한다고 가정했을 때,
이런 방식으로 그 많은 컴포넌트에 prop을 내려주어야 하는
너무 비효율적인 일이 발생할 것이다.

따라서 이러한 이유로 상태관리가 필요한 것인데,

Recoil을 사용함으로 인해
전처럼 isDark를 부모가 자식에게 prop을 내려주는 계층 구조 대신
atom을 형성하고 그 안에 있는 value가 필요한 경우 접근할 수 있게 하는 원리이다.
필요로 하는 component가 직접 atom에 연결되는 것이다.

 


Recoil을 사용하기 위해



터미널에  입력하여 설치한다.

npm install recoil



Recoil을 사용해보기 위해 전에 적어주었던 theme을 위해

prop을 일일히 전달해주었던 그 모든 과정들을 다 지워주도록 하겠다.


<App.tsx>
수정 전

function App() {
  const [isDark, setIsDark] = useState(false);
  const toggleDark = () => setIsDark((current) => !current);
  return (
    <>
      <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
        <GlobalStyle />
        <Router isDark={isDark} toggleDark={toggleDark} />
        <ReactQueryDevtools initialIsOpen={true} />
      </ThemeProvider>
    </>
  );
}

export default App;



수정 후

function App() {
  return (
    <>
      <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
        <GlobalStyle />
        <Router/>
        <ReactQueryDevtools initialIsOpen={true} />
      </ThemeProvider>
    </>
  );
}

export default App;



<Router.tsx>
수정 전

interface IRouterProps {
  toggleDark: () => void;
  isDark: boolean;
}

function Router({ toggleDark, isDark }: IRouterProps) {
  return (
    <BrowserRouter basename={process.env.PUBLIC_URL}>
      <Routes>
        <Route path={`/`} element={<Coins toggleDark={toggleDark} />} />
        <Route path="/:coinId/*" element={<Coin isDark={isDark} />} />
      </Routes>
    </BrowserRouter>
  );
}
export default Router;



수정 후

interface IRouterProps {

}

function Router({}: IRouterProps) {
  return (
    <BrowserRouter basename={process.env.PUBLIC_URL}>
      <Routes>
        <Route path={`/`} element={<Coins/>} />
        <Route path="/:coinId/*" element={<Coin/>} />
      </Routes>
    </BrowserRouter>
  );
}
export default Router;



<Coin.tsx>
수정 전

interface ICoinProps {
  isDark: boolean;
}

function Coin({ isDark }: ICoinProps) {
...
          {/* 중첩 라우팅*/}
          <Routes>
            <Route path="chart" element={<Chart isDark={isDark} coinId={coinId!} />} />
            <Route path="price" element={<Price />} />
          </Routes>
}




수정 후

interface ICoinProps {
  
}

function Coin({  }: ICoinProps) {
...
          {/* 중첩 라우팅*/}
          <Routes>
            <Route path="chart" element={<Chart coinId={coinId!} />} />
            <Route path="price" element={<Price />} />
          </Routes>
}





<Coins.tsx>
수정 전

interface ICoinsProps {
  toggleDark: () => void;
}

function Coins({toggleDark}:ICoinsProps) {
...
 <button onClick={toggleDark}>테마 변경</button>
}



수정 후

interface ICoinsProps {

}

function Coins({}:ICoinsProps) {
...
}


<Chart.tsx>
수정 전

interface ChartProps {
  coinId: string;
  isDark: boolean;
}

function Chart({ coinId, isDark }: ChartProps) {
...
options={{
            theme: {
              mode: isDark ? "dark" : "light",
            },
}


수정 후

interface ChartProps {
  coinId: string;
}

function Chart({ coinId }: ChartProps) {
...
options={{
            theme: {
              mode: false ? "dark" : "light",
            },
}




다 지워줬기 때문에 이제 index.tsx로 간다.

 


<index.tsx>
<RecoilRoot>로 감싸준다.

root.render(
  <RecoilRoot>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </RecoilRoot>
);



이제 드디어 아톰을 만들러 간다.

src 폴더 밑에 atoms.ts 파일을 생성해줌

 


<atom.ts>

import { atom } from "recoil";

export const isDarkAtom = atom({
  // key : "이름이고 유일해야함"
  // default : 기본적으로 둘 값
  key: "isDark",
  default: false,
});



아톰을 작성해주었기 때문에 이제는 App과 Chart를 atom과 연결해야 한다.

 

 


<App.tsx>

useRecoilValue function을 작성한다.

import { useRecoilValue } from "recoil";
import { isDarkAtom } from "./atom";

function App() {
  useRecoilValue(isDarkAtom);
  return (
    <>
      <ThemeProvider theme={false ? darkTheme : lightTheme}>
        <GlobalStyle />
        <Router />
        <ReactQueryDevtools initialIsOpen={true} />
      </ThemeProvider>
    </>
  );
}

export default App;



그러고선 value를 받아보자.
const isDark를 작성
어플리케이션이 isDarkAtom으로 연결되고, isDarkAtom의 기본값은 false이다.


function App() {
  const isDark = useRecoilValue(isDarkAtom);
  return (
    <>
      <ThemeProvider theme={isDark ? darkTheme : lightTheme}>
        <GlobalStyle />
        <Router />
        <ReactQueryDevtools initialIsOpen={true} />
      </ThemeProvider>
    </>
  );
}

export default App;



이제 Chart 탭을 isDarkAtom으로 연결할 차례이다.
useRecoilValue를 작성해준다.
그러고 밑에서 사용해주면 된다.

<Chart.tsx>

import { useRecoilValue } from "recoil";
import { isDarkAtom } from "../atom";


function Chart({ coinId }: ChartProps) {
...
  // 테마 적용을 위한 atom
  const isDark = useRecoilValue(isDarkAtom);
  
// 차트 속성 지정해주는 부분임
 options={{
            theme: {
              mode: isDark ? "dark" : "light",
            },
}




이제 atom.ts에서

export const isDarkAtom = atom({
  key: "isDark",
  default: false, // 이 부분이 바뀌면 theme이 전환된다.
});



default가 false이면 라이트모드, true로 바꿔주면 다크모드로 변하게 된다.


이번에는 Atom의 value를 어떻게 수정하는지 알아보자

Atom을 수정할 컴포넌트로 이동한다.


<Coins.tsx>
useRecoilValue를 쓰는 것은 아니고 비슷한 hook인 useSetRecoilState를 쓸 것이다.
useSetRecoilState(아톰이 들어간다);

import { useSetRecoilState } from "recoil";
import { isDarkAtom } from "../atom";

function Coins() {
...
  // setter function 은 value를 설정(set) 하는 function이다.
  const setDarkAtom = useSetRecoilState(isDarkAtom);

...
// button을 추가해주고 onClick에 setDarkAtom으로 현재에서 반대로 리턴해주면 된다.
<Header>
        <Link to={"/"}>
          <Title>Thorn Coin</Title>
        </Link>
        <SubTitle>Grab Your Own Coin!</SubTitle>
        <button onClick={() => setDarkAtom((prev) => !prev)}>Toggle Mode</button>
</Header>

 



-정리-
1.  App.tsx에서는 useRecoilValue를 통해 value를 가져오고 있고,


2. Coins.tsx에서는 function을 가져오는데, 이 function이 isDarkAtom 의 value를 수정할 것이다.
이 setDarkAtom function은 React의 setState와 같은 방식으로 작동하게 된다.

 

 

 

 

Recoil을 사용한 실행결과

 

 


한 세배정도 수고가 덜어진 느낌이다.

 



Comments