이 프로젝트를 진행한지 벌써 8개월이라는 시간이 지났고 그 동안 개인적으로 많은 성장이 있었다. 또한 8개월 전의 나와 지금의 나는 얼마나 다른 방식으로 코드를 바라보는지 어떤 부분을 중점적으로 코드를 작성하는지 등 개발자로서 성장하기 위해 꼭 되짚어봐야할 부분들이 많다고 생각했다.
그리고 만약 회사에서 레거시 코드를 마주했을 때 어떻게 대처해야할지 작게나마 미리 체험해볼 수 있을거같아서 전체적인 기존 코드 리팩토링을 하기로 결심했다. 전체 코드 리팩토링 후 몇 가지 새로운 기능도 추가할 예정이다.
oeoTravel
├─ App.js
├─ app.json
├─ assets
│ ├─ adaptive-icon.png
│ ├─ favicon.png
│ ├─ icon.png
│ ├─ splash.png
│ └─ ticket
│ ├─ hotel
│ │ ├─ 7-16.jpeg
│ ├─ plane
│ │ ├─ gTr.jpeg
│ └─ train
│ ├─ frankToUlm.png
└─ src
├─ component
│ ├─ common
│ │ ├─ header.js
│ │ └─ modal.js
│ ├─ context
│ │ └─ mapContext.js
│ ├─ home
│ │ ├─ homeScreen.js
│ └─ map
│ ├─ customs.js
├─ constant
│ └─ constants.js
└─ mock
├─ buttons.js
컴포넌트 디렉토리 안에서 home과 map 페이지별로 나눈거같은데 이렇게 되면 재사용 가능한 컴포넌트를 따로 분리해줄 디렉토리가 없어진다.
따라서 우선 pages
라는 디렉토리를 만들고 Home.jsx
페이지와 Map.jsx
페이지로 나누고 각 페이지에서 사용하는 컴포넌트들을 component
디렉토리에 분리해줘야한다.
└─ src
├─ component
│ ├─ home
│ ├─ map
└─ pages
│ ├─ Home.jsx
│ │─ Map.jsx
컴포넌트 디렉토리에 있는 home
과 map
디렉토리는 각 페이지에서 사용하는 작은 컴포넌트들을 넣을 예정이다.
나머지 디렉토리를 살펴보니 constants
, context
, mock
이 있었는데 각각 상수, context api관련 코드, 목데이터가 들어있었다. 먼저 context
를 살펴보았는데
import { useState, createContext } from "react";
import { maxCoor } from "../mock/latLng";
const MapContext = createContext(null);
function MapInfoContext(props) {
const [modalVisible, setModalVisible] = useState(false);
const [fitCoor, setFitCoor] = useState(maxCoor);
const [isPress, setIsPress] = useState(false);
const [modalId, setModalId] = useState("");
const mapInfo = {
modalVisible,
setModalVisible,
fitCoor,
setFitCoor,
isPress,
setIsPress,
modalId,
setModalId,
};
return (
<MapContext.Provider value={mapInfo}>{props.children}</MapContext.Provider>
);
}
export { MapContext, MapInfoContext };
이렇게 관심사 분리 없이 한곳에 모든 state와 action들이 뭉쳐있었다.
상태관리 라이브러리를 사용하기보단 context api와 useReducer를 결합해서 간단하게 정리하고자 했다.(예정)
다행히 한 컴포넌트에서 여러개의 기능을 수행하는 경우는 없었지만 여러개의 컴포넌트가 부모 컴포넌트에 뭉쳐있는 경우가 있었다.
import { Button } from "@rneui/themed";
import Styled from "styled-components/native";
import { View, Text } from "react-native";
import { Ionicons } from "@expo/vector-icons";
export function CustomMarker({ dayCount }) {
return (
<CustomMarkerWrapper>
<TextWrapper>
<Text
style={{
fontWeight: "700",
fontSize: 11,
color: "white",
}}
>
{` day${dayCount}`}
</Text>
<Ionicons name="chevron-forward-outline" size={14} color={"white"} />
</TextWrapper>
<Ionicons name="flag-outline" size={27} />
</CustomMarkerWrapper>
);
}
export function CustomCallout() {
return (
<View style={{ backgroundColor: "transparent" }}>
<Items title={"상세 보기"} />
</View>
);
}
export function Items({ onPressHandler, title }) {
return (
<Button
title={<ItemCard title={title} />}
onPress={onPressHandler}
titleStyle={{ fontWeight: "700", fontSize: 5 }}
buttonStyle={{
borderWidth: 0,
borderColor: "transparent",
borderRadius: 10,
backgroundColor: "#100f0f",
width: 135,
}}
containerStyle={{
width: "auto",
marginVertical: 2,
}}
icon={{
name: "angle-right",
type: "font-awesome",
size: 20,
color: "white",
}}
iconRight
iconContainerStyle={{ marginLeft: 30 }}
/>
);
}
function ItemCard({ title }) {
return (
<View style={{ flexDirection: "column" }}>
<Text
style={{
fontWeight: "bold",
fontStyle: "italic",
fontSize: 15,
color: "white",
}}
>
{title}
</Text>
</View>
);
}
여러가지 기능을 한곳에서 수행하는 컴포넌트는 아니지만
ItemCard
컴포넌트를 다른 곳에서 재사용하고 있었기 때문에 분리해줬다.
그리고 같은 함수를 같은 구조로 여러번 호출하는 코드가 있었다.
// Hotels.jsx
const hotelDate = [
{ date: "7/4", ticket: [require("../../../assets/ticket/hotel/7-4.jpeg")] },
{ date: "7/6", ticket: [require("../../../assets/ticket/hotel/7-6.jpeg")] },
{ date: "7/7", ticket: [require("../../../assets/ticket/hotel/7-7.jpeg")] },
{ date: "7/8", ticket: [require("../../../assets/ticket/hotel/7-7.jpeg")] }
];
export default function Hotels({ navigation }) {
return <SelectDate dates={hotelDate} navigation={navigation} />;
}
// Plane.jsx
const planeDate = [
{
date: "7/7",
ticket: [
require("../../../assets/ticket/plane/mTp1.png"),
require("../../../assets/ticket/plane/mTp1.png"),
],
},
{
date: "7/13",
ticket: [require("../../../assets/ticket/plane/gTr.jpeg")],
},
];
export default function Planes({ navigation }) {
return <SelectDate dates={planeDate} navigation={navigation} />;
}
date
데이터만 다르고 구조가 같은 컴포넌트가 4개나 존재했기 때문에 이를 하나의 유틸함수로 분리하고 데이터를 props로 받게 변경했다. 그리고 각 데이터는 mock 데이터에 따로 분류해서 import 해 사용했다.
// selectDate 컴포넌트
import { hotelDate, planeDate, ticketDate, trainDate } from "../mock/docData";
<SelectDate {...props} dates={hotelDate} />
각 디렉토리의 파일들을 index에서 한번에 export 해줬다.
export { default as DefaultHome } from "./DefaultHome";
export { default as TicketDetail } from "./TicketDetail";
export { default as SelectDate } from "./SelectDate/SelectDate";
└─ src
├─ component
│ ├─ Header.jsx
│ ├─ Modal.jsx
│ ├─ home
│ │ ├─ DefaultHome.jsx
│ │ ├─ SelectDate
│ │ │ ├─ DateCardTitle.jsx
│ │ │ └─ SelectDate.jsx
│ │ ├─ TicketDetail.jsx
│ │ └─ index.js
│ ├─ index.js
│ └─ map
│ ├─ Markers.jsx
│ ├─ customs
│ │ ├─ CustomCallout.jsx
│ │ ├─ CustomMarker.jsx
│ │ ├─ ItemCard.jsx
│ │ ├─ Items.jsx
│ │ └─ index.js
│ └─ index.js
├─ constants
│ └─ constants.js
├─ context
│ └─ mapContext.js
├─ mock
│ ├─ docData.js
│ ├─ latLng.js
│ └─ schedule.js
└─ pages
├─ Home.jsx
├─ Map.jsx
└─ index.js