오늘 배운 것 - ESLint, 토큰 재발급 로직, 디자인패턴, class

황토소금·2024년 8월 14일

TIL

목록 보기
16/49

#1

eslint 설정을 어떻게 더 해야할지 모르겠지만 아래와 같다.

{
  // https://docs.expo.dev/guides/using-eslint/
  "root": true,
  "extends": ["expo", "@react-native-community", "prettier"],
  "plugins": ["import", "prettier", "react"],
  "rules": {
    "import/no-unresolved": "error",
    "prettier/prettier": ["error", { "endOfLine": "auto" }],
    "react/react-in-jsx-scope": "off",
    "react-native/no-inline-styles": "off"
  },
  "settings": {
    "import/resolver": {
      "babel-module": {}
    }
  }
}

이럴 때! 예전 글에서 나왔던 module.export 라고 오타를 냈을 경우 이걸 eslint에서 잡아내지 못한다!
tsc는 잡아내는데 왜 eslint는 모르는가!

#2

react native에서 class를 만들 생각을 못했다.
문제는 다음과 같았다.
api 서버에 요청함 -> 엑세스 토큰이 만료되어 거절
이때 새 토큰으로 갱신함 -> 새 토큰으로 다시 같은 요청을 시도함 을 하고 싶었다.
이때 내가 생각했던 방식은 utils/app.js 내부에서 전체 접근 가능한, 모듈 스코프 차원에서 변수를 선언한 다음 토큰이 만료되었을 때 해당 변수를 업데이트하는 방법을 생각했다.

//utils/app.js
import ...
...생략
// 이렇게 모듈 스코프에 변수 선언
let recentAccessToken;

// asyncStorage에서 비동기적으로 값을 불러옴
AsyncStorage.getItem('accessToken').then(response => {
  recentAccessToken = response;
});

// 이후 토큰 처리와 관련된 코드들
const metadata = accessToken => {
  let headers = null;
  if (accessToken) {
    headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    };
  } else {
    headers = {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${recentAccessToken}`,
    };
  }

  return { headers };
};
...생략
const handleRequest = async request => {
  try {
    const response = await request();
    return response.data;
  } catch (err) {
    if (
      (err.response.status === 401 &&
        err.response.data.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
      err.response.data.detail === TOKEN_INVALID_TYPE_MESSAGE
    ) {
      try {
        const accessToken = await AsyncStorage.getItem('accessToken');
        const refreshToken = await AsyncStorage.getItem('refreshToken');
        const responseData = await axios.post(API_PATH.renew, {
          refresh: refreshToken,
          access: accessToken,
        });
        await AsyncStorage.setItem('accessToken', responseData.data.access);
        // 이렇게 새 토큰으로 갱신할 때 모듈 스코프에 정의된 recentAccesstoken을 재할당해주는 것이다!
        // accessToken은 만료기간이 짧으니 모듈스포크상에 정의해놓아도 되지 않을까
        recentAccessToken = responseData.data.access;
        const secondRequest = await request();
        return secondRequest.data;
      } catch (refreshError) {
        Sentry.captureException(err);

        if (refreshError.response.status === 401) {
          router.replace('index');
        } else {
          throw refreshError;
        }
      }
    }
    Sentry.captureException(err);
  }
};

그리고 블로그를 쓰면서 든 생각이 refreshToken은 만료기간이 길어 민감한 정보인데 코드에서 보듯

const refreshToken = await AsyncStorage.getItem('refreshToken');

이렇게 변수에 값으로 할당된 뒤에 변수를 다 사용하고 나면 할당 해제해줘야 가비지컬렉터가 그 값을 메모리에서 지워주는 것이 아닌가 생각을 했다. 그래서 피티와 얘기해보니 const로 선언된 변수는 재할당과 할당 해체를 임의로 할 수 없다는 거다!
대신 저 코드 상 refreshToken은 handleRequest 함수 내부에서 try 부분의 블록 스코프에서 정의되었기 때문에 해당 블록 스코프를 벗어나면 자동으로 할당 해제가 된다는 것이다!

멘토님은 아래와 같이 클래스로 처리하는 방법을 알려주셨다.

/**
 * Api 객체
 *
 * 1. headers를 매번 content-type, Authroization을 매번 사용처에서 넣어주지 않고 자동으로 관리하고 싶음
 * 2. 401 에러가 발생했을 때 에러를 공통으로 처리하고 싶음
 */

// /utils/api.js
class Api {
  constructor() {
    const init = async () => {
      this.accessToken = await AsyncStorage.getItem('accessToken');
      this.refreshToken = await AsyncStorage.getItem('refreshToken');
    };

    init();
  }

  get(url, options) {
    try {
      return axios.get(url, {
        ...options,
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.accessToken}`,
      });
    } catch (e) {
      if (axios.isAxiosError(e)) {
        if (
          (err.response.status === 401 &&
            err.response.data.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
          err.response.data.detail === TOKEN_INVALID_TYPE_MESSAGE
        ) {
          // Access Token 재발급

          this.accessToken = accessToken;
        }
      }
    }
  }

  // static getInstance() {
  //   if (!Api.~~~) {
  //     Api.instance = new Api()
  //   }
  //   return Api.instance
  // }
}

// export default new Api();

그리고 해당 코드를 설명해주시면서 export default new Api()로 하면 싱글톤 방식으로 구현할 수 있다고 하셨다.

팀원이 이후 이런 패턴을 실무에 어떻게 더 잘 적용할 수 있는지에 대한 질문을 했는데 인상깊었다. 나도 패턴은 어디서 주워들은건 있는데 이렇게 멘토님이 이건 싱글톤이 좋겠네요 하고 적용하는걸 보고 저런건 생각도 못했기 때문이다.
멘토님 왈 코드가 구현되는 방법에 따라 전형적으로 정해진 패턴들이 많다고 하다보면 는다고 하셨다.
e.g 읽는 행위가 많을 경우 싱글톤 어떤 이벤트의 변화에 따라 어떤 걸 해야 할 땐 옵저버 등등

profile
안녕하세요, 반갑습니다.

0개의 댓글