CSE Seat 컴포넌트를 리팩토링 해보자

Yoonlang·2022년 7월 3일
0

CSE_Seat

목록 보기
6/6

이번 시간에 리팩토링을 진행할 컴포넌트는 TodayInfo이다. 이 녀석을 첫 리팩토링 대상으로 고른 이유는 적당한 수의 useState와 recoilState이다. 또한 너무 복잡하지 않고 적절한 난이도라 생각한다.

현재 TodayInfo 컴포넌트 코드

이번 TodayInfo 컴포넌트 리팩토링의 목표

  1. 현재 컴포넌트의 길이가 너무 길다. 따라서 150줄 이하로 줄이겠다.
  2. pure function으로 만들 수 있다면 만들고 컴포넌트 외부에서 정의한다.
  3. fetch 함수를 따로 만들어서 길이를 줄인다.
  4. map을 통한 지저분하고 직관적이지 않은 jsx 처리는 새로운 컴포넌트를 생성한다.
  5. 리팩토링 전에 만든 jest 테스팅이 리팩토링 후에도 작동한다.

자 이제 시작해보자.

테스트 코드 작성

테스트 코드 작성에서 제일 중요한 포인트는 어떤 것을 테스트해야 하나? 라고 생각한다.
이는 개발자의 관점보다 사용자의 관점에서 생각하면 편하다.

아래 사진은 사용자가 볼 TodayInfo 컴포넌트의 UI이다.
TodayInfo
사진을 보며 테스트해야 할 내용을 살펴보자.

base

  1. 오늘, 내일, 1부, 2부 글씨를 볼 수 있다.
  2. 신청 기록 확인 버튼과 자리 취소 버튼을 볼 수 있다.
  3. 내가 예약한 좌석이 있다면 확인 가능하며 입퇴실 버튼이 있다.

user action

  1. 신청 기록 확인 버튼 누를 시 history 페이지로 넘어간다
  2. 자리 취소 버튼을 누를 시 입퇴실 버튼 란이 사라지고 체크박스가 생성되며 자리 취소 버튼은 취소하기 버튼으로 바뀐다.
    1. 체크박스를 누를 시 체크된다.
    2. 취소하기를 누를 시 체크한 시간의 자리는 취소되며 정보가 사라진다.

(과정)

테스트 코드를 작성 후

map을 통한 지저분하고 직관적이지 않은 jsx 처리는 새로운 컴포넌트를 생성한다.

를 처리한다.
이를 위해 기존에 Array.prototype.map으로 도배 된 컴포넌트를 새로 나눠준다.

todayInfoAfter

총 3개의 컴포넌트를 만들었으며, 필요한 데이터와 함수는 TodayInfo 컴포넌트가 통제한다. 기존의 구조보다 훨씬 직관적이고 깔끔하다. 또한 style jsx도 분리되면서 코드 길이가 훨씬 줄었다.

다음으로 처리할 것은 이것이다.

fetch 함수를 따로 만들어서 길이를 줄인다.

others 폴더에 fetch.js를 만든 후 fetch 함수를 따로 만들어줬다.

export const myFetch = async (method="GET", subURL="", body) => {
    if(method === "GET"){
        return await fetch(process.env.NEXT_PUBLIC_API_URL + subURL, {
            method: method
        })
    }
    return await fetch(process.env.NEXT_PUBLIC_API_URL + subURL, {
        method: method,
        credentials: "include",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify(body)
    })
}

다음으로는 pure function 만들기이다.
함수가 state와 관계없이 input으로만 처리한다면 고려 사항이 적어지고 개발 속도가 향상될 것이다. 이를 위해 가지고 있는 함수를 체크했다. 컴포넌트 내의 함수가 state와 recoilState를 처리하는 과정이 많다면 패스하고 그것이 아니라면 pure function으로 만들었다. 그 대상은 func handleCheckData이다.

const handleCheckData = (req) => {
    const tempInfoData = [];
    for (let i = 0; i < 4; i++) {
        tempInfoData.push(JSON.parse(JSON.stringify({
            isPart: false,
            showingData: {
                checkState: undefined,
                seatRoom: undefined,
                seatNum: undefined,
            },
            fetchingData: {
                buildingId: undefined,
                seatRoom: undefined,
                seatNum: undefined,
                isToday: undefined,
            },
        })));
    }

    req?.forEach(({ isToday, part1, part2, part1End, state }) => {
        const dayIndex = isToday ? 0 : 2;
        if (part1.isPart && !part1.cancel_marker && ((part2.isPart & !part1End) | (!part2.isPart))) {
            const tempData = tempInfoData[dayIndex]
            tempData.isPart = true;
            tempData.showingData.checkState = state;
            tempData.showingData.seatNum = part1.seat_num;
            tempData.showingData.seatRoom = part1.seat_room;
            tempData.fetchingData.buildingId = part1.building_id;
            tempData.fetchingData.isToday = isToday;
            tempData.fetchingData.seatNum = part1.seat_num;
            tempData.fetchingData.seatRoom = part1.seat_room;
        }
        if (part2.isPart && !part2.cancel_marker) {
            const tempData = tempInfoData[dayIndex + 1]
            tempData.isPart = true;
            if (isToday && part1.isPart && !part1.cancel_marker && !part1End)
                tempData.showingData.checkState = 3;
            else
                tempData.showingData.checkState = state;
            tempData.showingData.seatNum = part2.seat_num;
            tempData.showingData.seatRoom = part2.seat_room;
            tempData.fetchingData.buildingId = part2.building_id;
            tempData.fetchingData.isToday = isToday;
            tempData.fetchingData.seatNum = part2.seat_num;
            tempData.fetchingData.seatRoom = part2.seat_room;
        }
    });
    return tempInfoData
}

기존 함수와 다른 점은 useState를 쓰는 부분이 제외됐다는 것과 tempInfoData 만들 때 길이가 너무 길어 짧게 처리해 줬다는 점, 그리고 tempInfoData를 return 해주는 것이다.
tempInfoData를 처리하기 위해 컴포넌트 내부에서 기존에 setHandledInfoData를 처리하는 부분을 리팩토링해줬다.

useEffect(() => {
        if (checkData) {
            const tempInfoData = handleCheckData(checkData)
            setHandledInfoData(tempInfoData)
        }
    }, [checkData])

이렇게 리팩토링을 완료했고, 마지막으로 테스트 코드가 잘 돌아가는지 확인한다.

컴포넌트 리팩토링을 처음 해보았다. 기존 코드와 비교해서 그냥 봐도 굉장히 직관적으로 바뀌었고, 파일 분할을 하긴 했지만 컴포넌트의 코드 라인이 340줄에서 140줄로 줄었다.

테스트 코드는 아직 실력이 미흡해서 관련 코드나 설명이 아직 없다. 금방 채워 넣겠다.

참고

[번역] 리액트의 도(道) - Part 1

0개의 댓글