[Numble Challenge] 쿠팡 클론 코딩 1회차

정균·2022년 6월 30일
8
post-thumbnail

💻 Numble Challenge

React 가장 실무에 가까운 쿠팡 클론 코딩 챕터 I - 1회차

1회차 목표

로그인 페이지 - 좋은 ‘모듈' 설계하고 구현해보기
로그인을 위해 필요한 Data Fetching 모듈을 만들어볼 것입니다.
주어진 Interface에 맞게 Class 및 Function들을 만들어보며 지속가능한 모듈 설계와 프론트엔드에서의 객체지향에 대해서 고민해봅니다.

만들어볼 모듈

  1. (class) AuthService
  2. (Hook) useRequest

기간: 2022/06/17 ~ 2022/06/30

챌린지 링크

🔒 AuthService 리팩토링

auth.service.ts (리팩토링 전)

class AuthService {
  /** refreshToken을 이용해 새로운 토큰을 발급받습니다. */
  async refresh() {
    const refreshToken = cookies.get("refreshToken");
    if (!refreshToken) {
      return;
    }

    const { data } = await axios.post(
      process.env.NEXT_PUBLIC_API_HOST + "/auth/refresh",
      null,
      {
        headers: {
          Authorization: `Bearer ${refreshToken}`,
        },
      }
    );

    cookies.set("accessToken", data.access, { expires: 1 });
    cookies.set("refreshToken", data.refresh, { expires: 7 });
  }

  /** 새로운 계정을 생성하고 토큰을 발급받습니다. */
  async signup(
    email: string,
    password: string,
    name: string,
    phoneNumber: string,
    agreements: SignupAgreements
  ) {
    const { data } = await axios.post(
      process.env.NEXT_PUBLIC_API_HOST + "/auth/signup",
      { email, password, name, phoneNumber, agreements }
    );

    cookies.set("accessToken", data.access, { expires: 1 });
    cookies.set("refreshToken", data.refresh, { expires: 7 });
  }

  /** 이미 생성된 계정의 토큰을 발급받습니다. */
  async login(email: string, password: string) {
    const { data } = await axios.post(
      process.env.NEXT_PUBLIC_API_HOST + "/auth/login",
      { email, password }
    );

    cookies.set("accessToken", data.access, { expires: 1 });
    cookies.set("refreshToken", data.refresh, { expires: 7 });
  }
}

export default new AuthService();

AuthService 모듈 미션에서는 위와 같은 코드가 제공되었고, 주어진 코드를 재사용성, 확장성을 고려하여 재설계하는 것을 목표로 한다. 아래와 같은 부분들을 재설계했다.

1. type 분리

types/service.ts

type SignupAgreements = {
  privacy: boolean;
  ad:
    | {
        email: boolean;
        sms: boolean;
        app: boolean;
      }
    | false;
};

export interface ISignUpData {
  email: string;
  password: string;
  name: string;
  phoneNumber: string;
  agreements: SignupAgreements;
}

export interface ILoginData {
  email: string;
  password: string;
}

각 Service 클래스의 타입들을 types라는 폴더 안에 모아서 한번에 관리 할 수 있도록 했다. 타입에 대한 인터페이스의 이름 앞에 I를 붙여 알아보기 쉽게했다.

2. api.ts 분리

api.ts

import axios from "axios";
import { ISignUpData, ILoginData } from "./types/service";

export const refresh = (refreshToken: string) =>
  axios.post(process.env.NEXT_PUBLIC_API_HOST + "/auth/refresh", null, {
    headers: {
      Authorization: `Bearer ${refreshToken}`,
    },
  });
export const signUp = (signUpData: ISignUpData) =>
  axios.post(process.env.NEXT_PUBLIC_API_HOST + "/auth/signup", signUpData);
export const login = (loginData: ILoginData) =>
  axios.post(process.env.NEXT_PUBLIC_API_HOST + "/auth/login", loginData);
export const getMyData = (accessToken: string) =>
  axios.get(process.env.NEXT_PUBLIC_API_HOST + "/users/me", {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });
export const getReadData = (id: number) =>
  axios.get(process.env.NEXT_PUBLIC_API_HOST + "/users/" + id);

AuthService와 UserService에서 axios 통신을 하는 부분을 분리해서 api 파일을 새로 만들어줬다. 이전 코드에서는 Service들이 axios 라이브러리와 강하게 연결되어 있는데, 이를 분리 시켜서 의존성을 줄여주고 해당 요청들을 관리하기 쉬워지는 효과를 얻을 수 있었다.

3. Service 클래스들에 대한 부모 클래스 생성

service.ts

import cookies from "js-cookie";

class Service {
  setAccessToken(accessToken: string) {
    cookies.set("accessToken", accessToken, { expires: 1 });
  }
  setRefreshToken(refreshToken: string) {
    cookies.set("refreshToken", refreshToken, { expires: 7 });
  }
}

export default Service;

AuthService, UserService와 같은 Service 클래스들이 공통적으로 사용되는 기능들을 편하게 관리하기 위해 Service라는 부모 클래스를 만들고, 세부적인 Service 클래스는 이 Service 클래스를 상속하도록 한다. 다만 현재 주어진 서비스는 AuthService와 UserService 두 개 뿐인데, 딱히 공통적으로 관리할 기능이 안보여서 AuthService에서 계속 사용되는 토큰 적용 메소드만 가져왔다. 부모 클래스에 가져올 수 있는 부분을 더 생각해봐야 할 것 같다.

✉️ useRequest 설계

서비스에 useQuery를 그대로 적용한 코드

import { useQuery } from "react-query";

import { UserService } from "../src/services";

const Home: NextPage = () => {
  const { data: me } = useQuery("me", UserService.me, {
    refetchInterval: 500,
  });

  console.log("내 정보입니다", me);

	// ...
}

위 코드는 pages/index.ts에서 useQuery를 그대로 사용한 코드이다. 여기서 useQuery를 직접 사용하지말고 useRequest 모듈을 만들어서 다른 여러 요청에도 간편하게 적용할 수 있는 인터페이스를 구현하는 것을 목표로 한다.

useRequest.ts

import {
  QueryFunction,
  QueryKey,
  useQuery,
  UseQueryOptions,
} from "react-query";

export const useRequest = (
  key: QueryKey,
  func: QueryFunction,
  opt?: UseQueryOptions
) => useQuery(key, func, opt);

새로 설계한 useRequest는 위와 같다. 아직 react-query 라이브러리에 대한 지식이 부족하고 시간도 부족해서 일단은 useQuery를 단순 중계해주는 모듈로만 설계했다. 조금 더 공부한 후 새로 설계를 해봐야 할 것 같다.

🥲 느낀점

AuthService

사실 처음 미션을 받았을 때 조금 추상적이여서 어디서부터 뭘 시작해야할지 막막했다.. 이전에 리팩토링을 제대로 해본적도 없었고, 또한 사용자 인증(authorization)에 대해서도 잘 알지 못해서 주어진 코드가 제대로 작동하는 코드인지, 코드들이 무엇을 의미하는지 조차 알지 못했다.

코드를 이해하고 싶어서 사용자 인증의 기본적인 내용을 공부했고 공부한 점에 대해 블로그도 포스팅했다.
사용자 인증의 기본
사용자 인증에 대해 공부했지만 이번 리팩토링을 하는데에는 딱히 큰 도움이 되지 못했다..ㅋㅋ; 하지만 새로운 지식을 배웠으니 이것만으로 만족한다.

리팩토링에 대해서도 구글링을 하면서 다른 사람들은 어떻게 리팩토링했는지 보면서 열심히 공부했다. 그렇게 해서 배운점에 대해 최대한 적용해봤다. 아직은 표면을 사포질하는 수준으로만 리팩토링 했지만, 나중에는 더 멋진 구조의 코드를 완성할 수 있다면 좋을 것 같다.

useRequest

useRequest 부분은 거의 건들지 못해서 많이 아쉬웠다. 1회차 챌린지 기간 중 첫 일주일 동안은 시험기간이라 아예 시작도 못했고, 나머지 기간 마저 다른 할 일에 많이 치여서 제대로 시작하지 못했다.. 원래 계획상으로는 리액트 쿼리 라이브러리에 대해 공부를 많이하고 이를 적용해서 useRequest 모듈을 제작하려고 했지만 AuthService 부분에 시간을 많이 쏟아서 리액트 쿼리 공부도 거의 못했다ㅠ 조금 더 공부하고 다른 넘블러 분들의 코드도 참고하면서 새로 설계 해봐야겠다.

깃허브 링크

profile
💻✏️🌱

0개의 댓글