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는 모르는가!
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 읽는 행위가 많을 경우 싱글톤 어떤 이벤트의 변화에 따라 어떤 걸 해야 할 땐 옵저버 등등