ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ContextAPI와 Recoil
    개발공부/기타 2024. 1. 15. 16:47

    지금 지인들과 진행하고 있는 사이드 프로젝트가 하나있다.

    처음에는 관리해야 할 데이터가 별로 없어서 ContextAPI를 사용했는데,

    시간이 지날수록 불편한 점들이 있어서 팀원들과 논의한 결과 Recoil을 사용하기로 결정했다! :)

     

    변경한 이유는?

    ContextAPI를 불편하게 생각했던 이유는 아래와 같다.

    1. 코드 간결성 및 가독성
    2. 불필요한 리렌더링 발생

     

    코드 간결성 및 가독성

    ContextAPI에 로그인 유무를 저장한 변수가 있었는데 분명 boolean으로 저장했는데 console에 찍으면 int이 찍혀있는 것이었다.

     

    디버깅을 통해 원인을 파악해보니 팀원 중 한명이 코드를 잘못 작성해서 int형으로 데이터가 잘못들어갔던 것이다. (주석 참고)

    export const AuthProvider = ({ children }: any) => {
        const [isLoggedIn, setIsLoggedIn] = useState(false);
    
        // ... 생략
    };
    
    export const useAuth = () => {
        const context = useContext(AuthContext);
    
        if (!context) {
            throw new Error("useAuth must be used within an AuthProvider");
        }
    
        const { userInfo, isLoggedIn, ...rest} = context;
        
        // 문제 코드: && 연산자는 두 조건이 모두 참일 때 뒤에 조건 값을 변수에 저장
        const isUserInfoValid = userInfo && userInfo.sub;
    
        return { userInfo, isLoggedIn: isLoggedIn && isUserInfoValid, ...rest };
    };

     

     

    디버깅 과정 중 ContextAPI 코드가 가독성이 좋지 않아 간단한 문제의 원인을 빠르게 파악하지 못했다.

    (위의 코드는 생략된 부분이 있기 때문에 눈에 보이지만.. 실제 코드는...ㅎㅎ)

     

     

    불필요한 리렌더링 발생

    ContextAPI의 경우 상태 값을 업데이트 하는 경우 Context를 구독하는 컴포넌트들이 모두 리렌더링 된다.

    우리 프로젝트의 경우 모든 컴포넌트가 Context API를 구독하는 형태라서.. 불필요한 리렌더링이 너무 많이 발생된다.

    return (
        <QueryClientProvider client={queryClient}>
            <AuthProvider>
                {/* React Query Devtools: 필요한 경우 주석 해제 후 사용 */}
                {/* <ReactQueryDevtools initialIsOpen={false}/> */}
                <Layout>{isLoading ? <Spinner /> : <Component {...pageProps} />}</Layout>
            </AuthProvider>
        </QueryClientProvider>
    );

     

     

    왜 Recoil?

    우리 팀이 Recoil을 선택한 이유는 Redux에 비해 간결한 코드 및 API를 가졌기 때문이다.

     

    둘다 중앙 집중환된 상태 관리를 지원하지만, Redux는 초기에 정의해야 할 내용이 많다. 

    (액션 타입 및 생성자 함수 정의, 리듀서 함수 작성 등) 

     

    Recoil이란?

    Recoil은 Facebook에서 개발한 React 상태 관리 라이브러리이다.

    • 상태를 중앙 집중화하여 여러 컴포넌트 간에 공유할 수 있도록 지원
    • Atom과 Select라는 개념을 제공하여 상태 구조화 및 재사용성을 높임
      • Atom: 상태를 나타냄
      • Select: 파생된 상태를 만들기 위한 함수
    • 필요한 부분만 리렌더링 되도록 최적화되어 있음
    • 브라우저의 DevTools와 통합되어 디버깅이 용이하여 상태 변화를 쉽게 추적하고 디버깅할 수 있음

     

    Atom

    • 상태(state)를 정의하는 개념
    • 컴포넌트에서 구독과 업데이트가 가능하며 atom의 값이 변경될 시 구독하고 있는 컴포넌트가 리렌더링 됨
    // 읽기 및 쓰기 | Recoil에 저장된 값, 값 변경하는 함수
    const [recoilState, setRecoilState] useRecoilState(definedAtom);
    
    // 읽기 | Recoil에 저장된 값
    const recoilState = useRecoilValue(definedAtom);
    
    // 쓰기 | Recoil에 저장된 값 변경
    const recoilState = useRecoilValue(newAtom);
    
    // 초기화 | Recoil에 있는 default 값으로 초기화
    const reset = useResetRecoilState(definedAtom);

     

    Selector

    • atom을 기반으로 파생된 데이터를 만들어내는 데 사용
    • 기존 atom은 유지되면 파생된 데이터를 가져옴
    export const testSelector = selector({
          key: "key", // atom을 식별하는 고유 key
          get: ({ get }) => { // 원하는 로직 작성
          		const data = get(myAtom);
                return data;
          },
    });

     

     

    프로젝트 적용하기

    자, 이제 Recoil에 대해 공부 했으니 실제 프로젝트에 적용할 차례이다. 

     

    1. Atom 만들기 (/src/recoil/atoms/currentUser.tsx)

    import { CurrentUserType } from "@/types/userTypes";
    import { atom } from "recoil";
    import { recoilPersist } from "recoil-persist";
    
    // recoilPersist 함수 호출하여 설정 객체 생성
    // 새로고침 시에도 초기화되지 않도록 하는 옵션인 effects_UNSTABLE을 사용하기 위해 선언
    const { persistAtom } = recoilPersist({
    });
    
    // atom 정의
    export const currentUser  = atom<CurrentUserType>({
        key: "currentUser", // atom을 식별하는 고유 key
        default: { // 최초의 atom 기본 상태 (초기 설정 값)
            isLoggedIn: false,
            userId: "",
            nickname: "",
            age: "",
            auth: "",
            interest: "",
            profileImageUrl: "",
        },
        effects_UNSTABLE: [persistAtom], // 새로고침 시에도 초기화되지 않도록 설정
    });

     

    2. Recoil 상태 구독 및 데이터 설정 (useRecoilState hook 사용)

    // useRecoilState hook 사용하여 구독
    const [userState, setUserState] = useRecoilState(currentUser);
    
    const loginMutation = useMutation(getKakaoLogin, {
        onSuccess: () => {
            const token = getSession("access_Token");
            if (token) {
                const payload = token.split('.')[1];
                const decodedPayload = decode(payload);
                const payloadObject = JSON.parse(decodedPayload);
                setUserState({ isLoggedIn: true, ...payloadObject}); // Recoil에 데이터 저장
    
                // 처음 로그인한 사용자
                if (!payloadObject.age) router.push("/userinfo-setting");
                else router.push("/main");
            }
        },
        onError: () => {
            alert("로그인이 실패했습니다. 다시 시도해주세요.")
            router.push("/users/login");
        },
    });

     

    3. 저장한 데이터 가져오기 (useRecoilValue hook 사용)

    import { useRecoilValue } from "recoil";
    import { currentUser } from "@/recoil/atoms/currentUser";
    
    export function GetCurrentUser() {
        const userInfo = useRecoilValue(currentUser);
        return userInfo;
    }

     

    728x90

    '개발공부 > 기타' 카테고리의 다른 글

    Styled Components와 Tailwind CSS  (0) 2024.01.10
    HTTP 에러  (2) 2024.01.05
Designed by Tistory.