뒤로가기
🔴 개관
저는 프로그래밍을 할 때 단순히 돌아가기만 하면 된다는 접근보다, 가독성과 코드의 효율성을 더욱 중요하게 여깁니다.
처음 React Native로 프로젝트를 진행했을 때, React와 달리 데이터를 저장하고 사용하기 위해 복잡한 코드를 작성해야 했던 경험이 있습니다. 당시에는 효율성보다는 기능 구현에 초점을 맞췄기 때문에 코드가 비효율적이었습니다. 초보자라면 누구나 겪을 문제라고 생각합니다.
그러나 이번에 React Native로 두 번째 프로젝트를 진행하며, 비효율적인 AsyncStorage의 저장 및 조회 코드를 모듈화하여 재사용성과 유지보수성을 높이겠다고 계획했습니다.
🔴 첫 번째 React Native 프로젝트: 완전한 스파게티 코드
오랜만에 캡스톤에서 진행했던 프로젝트의 코드를 꺼내보았습니다.
React Native를 처음 사용해보는거라서 아주 스파게티 코드입니다.
async function onPressAlertRegister() {
AsyncStorage.getItem('userState', async (err, result) => {
const resultData = JSON.parse(result);
const res = await RegisterPetFunction({
uuid: resultData.uuid,
name,
birth,
kind,
gender,
});
if (res.statusCode === '200') {
Alert.alert(
'등록 성공',
'반려동물 등록을 완료하였습니다.',
[
{
text: '확인',
onPress: () => {
setNull();
navigation.dispatch(CommonActions.navigate('OwnerAccount'));
},
style: 'destructive',
},
],
{
cancelable: true,
onDismiss: () => {},
},
);
} else {
Alert.alert(
'등록 실패',
'반려동물 등록을 완료하지 못했습니다.',
[
{
text: '확인',
onPress: () => {},
style: 'destructive',
},
],
{
cancelable: true,
onDismiss: () => {},
},
);
}
});
}
지금 생각하면 아주 끔찍한 코드입니다.
서버에서 오는 응답에 따른 두번의 Alert 코드와 그 Alert을 감싸고있는 AsyncStorage 함수의 모습입니다....
초반에는 기능 구현에만 집중했기 때문에 비효율적인 코드가 발생하였습니다.
저는 여기서 Alert나 AsyncStorage에 대한 모듈화가 필요하다고 생각하였지만
기간이 빡빡하기도 하였고, 저의 실력 문제;;로 진행하지는 못하였습니다.
🔴 두번째 React Native 프로젝트: 코드 모듈화
사실 첫번째 어플리케이션을 리팩토링하자! 하는 마음이 지금도 남아있지만....
AI 서버와 백엔드 서버가 다 내려갔기 때문에 리팩토링, 유지보수 한다고 해도 돌아간다는 보장이 없습니다.
모듈화의 기회는 두번째 프로젝트에서 달성할 수 있었습니다.
저는 제일 문제된다고 생각한 것이 AsyncStorage 함수와 Alert 함수라고 생각했습니다.
그래서 프로젝트에 Utils 폴더를 만들어 전역적으로 이 함수들을 관리하기로 결심했습니다.
Alert 컴포넌트 코드를 먼저 보여드리겠습니다.
import {Alert} from 'react-native';
/**
* 확인 버튼이 있는 기본 알림창 컴포넌트
*
* 이 컴포넌트는 제목과 메시지를 포함하는 알림창을 생성하고, 확인 버튼을 클릭했을 때 지정된 함수가 실행
*
* @param {Object} props - 컴포넌트에 전달되는 props
* @param {string} props.title - 알림창의 제목
* @param {string} props.message - 알림창의 설명 메시지
* @param {function} props.onPress - 확인 버튼을 눌렀을 때 호출되는 함수
*
* @returns {void} - 알림창을 화면에 표시하며, 함수 호출로 알림창을 표시
*/
function ConfirmAlert({title, message, onPress}) {
function showAlert() {
Alert.alert(
title, // 알림창 제목
message, // 알림창 설명
[
{
text: '확인',
onPress: onPress,
style: 'cancel',
},
],
{cancelable: true}, // 알림창 외부 클릭 시 취소 가능
);
}
return showAlert();
}
/**
* 확인 및 취소 버튼이 있는 기본 알림창 컴포넌트
*
* 이 컴포넌트는 제목과 메시지를 포함하는 알림창을 생성하고, 확인 및 취소 버튼을 클릭했을 때 각각 지정된 함수가 실행
*
* @param {Object} props - 컴포넌트에 전달되는 props
* @param {string} props.title - 알림창의 제목
* @param {string} props.message - 알림창의 설명 메시지
* @param {function} props.onPressConfirm - 확인 버튼을 눌렀을 때 호출되는 함수
* @param {function} props.onPressCancel - 취소 버튼을 눌렀을 때 호출되는 함수
*
* @returns {void} - 알림창을 화면에 표시하며, 함수 호출로 알림창을 표시
*/
function CancelAlert({title, message, onPressConfirm, onPressCancel}) {
function showAlert() {
Alert.alert(
title, // 알림창 제목
message, // 알림창 설명
[
{
text: '취소',
onPress: onPressCancel,
style: 'cancel'
},
{
text: '확인',
onPress: onPressConfirm,
},
],
{cancelable: true}, // 알림창 외부 클릭 시 취소 가능
);
}
return showAlert();
}
export {ConfirmAlert, CancelAlert};
확인 버튼이 있는 알림창과 확인, 취소 버튼이 있는 알림창 컴포넌트를 만들었습니다.
두 파일로 나눠서 만들까 하다가
- Utils/component 폴더 내부에 또 Alert 폴더를 만들기 싫어서
- 한 파일만 임포트하면 두 Alert 컴포넌트 접근 가능해서
위의 이유로 한 파일에 합쳐 작성했습니다.
아래 코드는 위 컴포넌트 사용 예시입니다.
치매 진단지를 서버로 제출하는 함수인데, 서버에서 오는 응답 코드에 따라 다른 Alert 컴포넌트를 띄우거나 탭을 이동하는 코드입니다.
async function getNextDiagnosisSheet() {
if (num < 11) {
setNum(prev => prev + 1);
} else {
CancelAlert({
title: '진단지 제출',
message: '진단지를 제출하시겠습니까?',
onPressConfirm: async () => {
const result = await PostDiagnosisAnswer({
diagnosisAnswer: diagnosisAnswer,
});
if (result.statusCode === '200') {
navigation.navigate('Diagnosis Result', {result});
setNum(1);
return;
}
if (result.statusCode === '400') {
ConfirmAlert({
title: '치매 진단 실패',
message: '진단 결과 저장 및 치매 진단에 실패하였습니다.',
onPress: () => {},
});
}
if (result.statusCode === '426') {
ConfirmAlert({
title: '유효하지 않은 점수',
message: '점수가 유효하지 않습니다.',
onPress: () => {},
});
}
},
onPressCancel: () => {},
});
}
}
물론! 저 함수에 title, message, onPress 세 개의 파라미터가 들어가기 때문에 모듈화를 시켜 사용한다고 해도 여전히 코드가 어지럽고 길긴 합니다.
하지만 제 실력으로는 최선이었습니다.
하지만 모듈화를 안하고 사용했다면? 진짜 아찔합니다.
다음으로는 절 힘들게했던 AsyncStorage 함수입니다.
React와는 다르게 ReactNative에서는 async가 필요해서 코드가 굉장히 복잡해졌습니다.
import AsyncStorage from '@react-native-async-storage/async-storage';
export const Storage = {
/**
주어진 키와 값을 AsyncStorage에 저장
@param {string} key - 저장할 데이터의 키
@param {any} value - 저장할 데이터 값 (자동으로 JSON 문자열로 변환됨)
@returns {Promise<void>} 저장 완료 시 resolve
*/
async setItem(key, value) {
try {
await AsyncStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('[Error] Failed to save data', error);
}
},
/**
주어진 키로 AsyncStorage에서 데이터를 가져옴
@param {string} key - 조회할 데이터의 키
@returns {Promise<any|null>} 조회된 데이터 (JSON 문자열을 파싱하여 반환), 데이터가 없으면 null
*/
async getItem(key) {
try {
const value = await AsyncStorage.getItem(key);return value ? JSON.parse(value) : null;
} catch (error) {
console.error('[Error] Failed to fetch data', error);
}
},
/**
주어진 키로 AsyncStorage에서 데이터를 삭제
@param {string} key - 삭제할 데이터의 키
@returns {Promise<void>} 삭제 완료 시 resolve
*/
async removeItem(key) {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.error('[Error] Failed to remove data', error);
}
},
/**
AsyncStorage의 모든 데이터를 삭제
@returns {Promise<void>} 삭제 완료 시 resolve
*/
async clearAll() {
try {
await AsyncStorage.clear();
} catch (error) {
console.error('[Error] Failed to clear data', error);
}
},
};
여기서 흠은 console을 띄웠다는 점인데요.. 다음은 에러가 나온다고 하면 Alert창을 띄우거나.. 다시 시도하거나 하는 방식을 해봐야겠습니다.
Storage 싱글톤 객체를 만들어서 내부에 네가지 메서드를 넣었습니다.
한창 정보처리기사 공부할때 디자인패턴이 나오길래 신나했던 기억이 납니다..
주로 사용했던 메서드는 setItem과 getItem 입니다.
async function Logout() {
CancelAlert({
title: '로그아웃',
message: '정말 로그아웃 하시겠습니까?',
onPressConfirm: async () => {
Storage.setItem('userState', {
jwtToken: null,
uuid: null,
isLoggedIn: false,
});
navigation.navigate('Login');
return;
},
onPressCancel: () => {},
});
}
위에는 아까 봤던 Alert 컴포넌트와 Storage 의 setItem 메서드가 결합된 코드입니다.
엄청 깨끗해지고 가독성도 좋아졌습니다. (맞나..?)
🔴 결론
저는 캡스톤에서 처음으로 React Native를 사용해봤습니다.
기간이 부족했기 떄문에 몸통박치기 수준이 아니고 몸통 갈아넣기 정도로 일단 써보자 마인드였는데요
만약에 프론트가 저 혼자가 아니고 여러명이서 진행했다면 프론트끼리 머리채잡고 싸웠을수도 있습니다.
이런 스파게티 코드는 물론 개발 진행에 있어서 굉장히 불가피하다고 생각합니다.
하지만 계속해서 코드를 리팩토링하고 유지보수한다면 전보다는 실력이 10%정도.. 아님 1%라도 성장하는 것 같습니다.
요즘 저는 리팩토링을 진행했다고는 하지만 지금보면 발싸개같은 코드를 다시 보면서..
React 디자인패턴 공부에 박차를 가해야겠다는 생각이 듭니다....
저의 시행착오가 여러분들에게 도움이 되셨으면 좋겠습니다.
프론트엔드 카테고리와 관련된 최신 글
피크민 블룸은 어떻게 꽃길🌸을 남길까?
Next.js 마이그레이션: 서버 컴포넌트(SSC)와 클라이언트 컴포넌트(CSC) 선택하기
최고의 프로젝트 구조를 찾아서....
React Query로 데이터 처리의 효율성 높이기
스파게티 코드 갱생시키기 프로젝트