본문 바로가기

growth-log

Recoil_ React상태 관리

리코일은 리액트를 개발한 메타(페북??) 에서 개발한 전역적 상태관리 도구로, 프로젝트 전체에서

상태를 전역적인 트리로서 관리할 수 있다. 근래에는 다른 관리도구들이 선호되고 있어 흔히 사용되고

있지는 않지만 Redux에 비해 사용방법이 쉬워 보여서 접근해보았다.

 

프로젝트에 리코일을 설치하고 난 후, 리액트 프로젝트의 App설정에서 리코일의 Root트리로서

감싸주면 전역적으로 구동하도록 설정된다. 리액트의 상태값은 atom을 통해 초기화할수 있으므로

Store를 구성하여 상태의 단위별로 atom을 구성한다. 이렇게 구성된 상태의 변수값을 컴퍼넌트에서

사용할수 있으며, 특정 컴퍼넌트에서 상태값이 업데이트되면 관련 컴퍼넌트에서 구독의 방식으로

전해지고 리렌더가 발생하면서 전체 화면에서 동일한 상태를 유지한다.

 

1) 리코일 설치 후 트리 설정 

리액트 프로젝트의 App설정에 Recoil의 RootTree의 컴퍼넌트로서 감싸주는 트리설정을

통해 변경이 발생되면 관련 컴퍼넌트에 속속 전파된다.  

 

- App설정에 RecoilRoot추가 

// 여러 라이브러리 설정을 import생략, 컴퍼넌트생략 .... 
import { RecoilRoot } from "recoil";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <div>
      <RecoilRoot>
        <ApolloSetting>
          <>
            <Layout>
              <Component {...pageProps} />
            </Layout>
          </>
        </ApolloSetting>
      </RecoilRoot>
    </div>
  );
}

 

 

2) 관리할 Status의 설정과 Value의 초기화  

(1) Atom

atom은 리코일 상태관리의 핵심이다. 어떤 상태를 관리할 것인지와 초기값을 어떻게 설정할

것인지를 결정하며 해당 상태를 이용하는 컴퍼넌트에서 상태값을 변경하는 경우 이를 감지하여

업데이트하는 저장소 역할을 한다. 

뿐만 아니라 상태값을 사용하는 컴퍼넌트에 값변경을 공유하여 렌더링을 유도하는 역할도 한다.

RecoilValue로는 값을 읽어들일수 있고, useRecoilState로는 값을 업데이트 할수도 있다.

 

상태는 unique한 문자로서 상태를 식별하는 구분자인 key와 상태의 value로서 관리되며,

key와 default (상태의 초기값)이 객체로 설정되어 atom에 인자로서 전달하여 생성한다. 

 

(2)Selector

비동기 통신으로 받아온 결과를 전역적으로 활용하기 위해 상태로서 관리하는 함수이다. 

구분자인 Key값과 비동기 함수가 value로서 설정된다. 특정한 엔드포인트와의 통신을 중복으로

수행하지 않고 상태값으로서 전역적으로 활용할수 있다.  

 

- Store로서 atom과 selector를 구성

   ( Selector는 세션을 이용해 새로운 Token을 발급받는 과정으로 작성 ) 

import { atom, selector } from "recoil"; //recoil에서 정해주는 변수를 주는 함수
import { getAccessToken } from "../../components/commons/libraries/getAccessToken";

export const isEditState = atom({
  key: "isEditState",
  default: true,
});

export const userNameState = atom({
  key: "userNameState",
  default: "",
});

export const userEmailState = atom({
  key: "userEmailState",
  default: "",
});

export const userPasswordState = atom({
  key: "userPasswordState",
  default: "",
});

export const accessTokenState = atom({
  key: "accessTokenState",
  default: "",
});

export const visitedPageState = atom({
  key: "visitedPageState",
  default: "",
});

export const restoreAccessTokenLodable = selector({
  key: "restoreAccessTokenLodable",
  get: async () => {
    const newAccessToken = await getAccessToken();
    return newAccessToken;
  },
});


// getAccessToken
export const getAccessToken = async (): Promise<string | undefined> => {
  try {
    const graphQLClient = new GraphQLClient(
      "https:// 토큰을 발행할 URL ",
      { credentials: "include" },
    );
    const result =
      await graphQLClient.request<Pick<IMutation, "restoreAccessToken">>(
        RESTORE_ACCESS_TOKEN,
      );
    const newAccessToken = result.restoreAccessToken.accessToken;
    return newAccessToken;
  } catch (error) {
    if (error instanceof Error) console.log(error);
  }
};

 

 

3) 컴퍼넌트에서 State 표시와 업데이트   

atom에서 설정한 상태값의 변수명을 useRecoilState훅에 인자로서 설정하고 변수로 받아

recoil에 설정된 상태값을 가져올수 있으며, 상태변경 함수를 연동할수 있다.(setter함수)

이렇게 연동되면 (구독과 유사한 기능으로서) 다른 컴퍼넌트에서 value가 변경되는 경우

값 변경을 통보받아 화면을 리렌더 한다.

세터 함수를 받아오는 경우 컴퍼넌트 내에서 값을 변경할수 있으며 이러한 update는 atom에서

처리되어 구독중인 다른 컴퍼넌트로 전파된다.  

 

- 로그인 페이지 (사용자 정보를 API를 통해 전달하고 결과로 인증 토큰을 받아 Recoil에 저장)

//로그인을 통해 받은 토큰을 Recoil에 전역적으로 저장 
import { useRecoilState } from "recoil";
import {
  accessTokenState,
  userEmailState,
  userNameState,
  userPasswordState,
} from "../../../src/commons/store";
import { gql, useMutation } from "@apollo/client";
import type { ChangeEvent } from "react";
import { useState } from "react";
import {
  IMutation,
  IMutationLoginUserExampleArgs,
} from "../../../src/commons/types/generated/types";
import { useRouter } from "next/router";
import { wrapAsync } from "../../../src/components/commons/libraries/asyncFunt";

const LOGIN_USER = gql`
  mutation loginUserExample($email: String!, $password: String!) {
    loginUserExample(email: $email, password: $password) {
      accessToken
    }
  }
`;

export default function Login() {
  const router = useRouter();
  const [email, setUserEmail] = useState("");
  const [password, setPassword] = useState("");
  const [, setAccessToken] = useRecoilState(accessTokenState);
  const [loginUserExample] = useMutation<
    사용자의 이메일과 비밀번호를 백엔드에 전달하고 결과값을 받아오는 설정
  >;

  const onChangeUserEmail = (event: ChangeEvent<HTMLInputElement>) => {
    setUserEmail(event.target.value);
  };

  const onChangeUserPassword = (event: ChangeEvent<HTMLInputElement>) => {
    setPassword(event.target.value);
  };

  const onClickLoginSubmit = async (): Promise<void> => {
    try {
      console.log(email, password);
      const result = await loginUserExample({
        variables: {
          email,
          password,
        },
      });
      const access_token = result.data?.loginUserExample.accessToken;
      if (access_token == undefined) {
        alert("로그인에 실패했습니다. 다시 시도해주세요");
        return;
      }
      setAccessToken(access_token);
      void router.push("/로그인에 성공하는 경우 표시할 컴퍼넌트의 주소");
    } catch (error) {
      if (error instanceof Error) alert(error.message);
    }
  };

  return (
    <>
      이메일 : <input onChange={onChangeUserEmail} type="text" />
      암호: <input onChange={onChangeUserPassword} type="text" />
      <button onClick={wrapAsync(onClickLoginSubmit)}>제출</button>
    </>
  );
}

 

______________________________________________________________________

Redux에서도 통신결과를 전역적으로 활용하기 위한 설정이 있고, 이러한 부분을 처리하면서 많은

오류가 생겼다. 특히 통신 결과를 받아와서 편집하는 부분에서 오류가 발생하거나 예외처리를 할때

Typescript적용에 대한 설명이 없어서 코드가 무한정으로 길어졌었다.

Recoil은 Selector를 이용해 통신 요청을 하고 lodable로서 처리하는데 이 부분은 원리가 정확히

이해되지는 않는다. 향후 전역관리 Tool을 보다 다양하게 적용해보면서 연습해보려고 한다. 

'growth-log' 카테고리의 다른 글

반도체의 유리기판 ?  (0) 2025.02.15
next의 ssr, csr로 인한 오류제어  (0) 2025.02.04
CJS와 ESM  (0) 2025.01.22
방송통신대 CS 마지막 학기의 후기  (2) 2024.12.16
Youtube 데이터 크롤링  (3) 2024.10.12