안녕하세요 우테코 6기 FE 바다입니다!.
타입스크립트, 컴포넌트, cypress를 통한 E2E테스트를 진행한 "점심 뭐먹지?"미션이 끝이 나서 미션을 진행하면서 알게 된 점, 겪은 이슈와 느낀 점들을 이야기 해보려 합니다.
🖱️ 점심 뭐먹지? 배포 페이지 바로가기
🖱️점심 뭐먹지? 저장소 바로가기
이번 미션을 시작하면서 바닐라js로 컴포넌트를 만들 수 있는 법을 찾았고, custom element의 존재를 알게 되었고 이를 잘 활용하며 재사용성을 챙길 수 있다고 생각해 페어와 상의 후 도입해보기로 했습니다.
미션 구현을 하면서 느낀 custom element는 props와 레이아웃 구조가 간단할 수록 재사용하기 사용하기 좋지만 반대로 props로 넘겨주어햐는 것들이 짧은 string타입이 아니거나(ex:배열, 함수) 레이아웃이 복잡하다면 custom element는 불편한 컴포넌트라는 것입니다.
상황
select 요소가 들어 있는 dropBox 컴포넌트를 connectedCallback으로 생성해주고 , select에 change 관련 이벤트 리스터를 넣어주려 했습니다.
class AllRestaurantList extends RestaurantListTemplate {
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
//....
// filtering, sorting dropbox 추가
this.#addDropBoxGroupToContainer();
//dropbox 내 select에 이벤트 추가
this.#addEventToFilteringAndSorting();
원인
connectedCallback을 통해서 drop box를 음식점 리스트의 형제 요소로 넣어주었지만 아직 drop box가 DOM에 구현된 것이 아니라서 connectedCallback 내에서 실행한 이벤트 리스너는 drop box내의 select 요소를 찾을 수 없었습니다.
해결방법
위의 해결방법으로는 비동기로 이벤트를 주거나, Observer를 사용하는 것이 있습니다. 저는 보다 간편한requestAnimationFrame
를 사용해 오류를 해결했습니다.
connectedCallback() {
// 다음 프레임에서 실행되도록 예약
requestAnimationFrame(() => {
const selectElement = this.querySelector('drop-box select');
console.log(selectElement);
});
}
이번 미션에서 음식점 입력폼 모달과 음식점 상세정보 모달이 존재해서 모달 컴포넌트가 자식요소를 받아서 사용할 수 있게 하고 싶었습니다. (자식 요소로 다양한 모달 내용을 변경될 수 있도록 함) 그래서 shadow와 slot을 이용했습니다.
shadowRoot를 통해서 상황에 따라 필요한 자식요소를 추가할 수 있지만, 추가 해준 자식요소내의 있는 다른 자식요소에 접근 시 다소 불편한 점들이 있었습니다.
document.querySeletor등의 DOM에 접근하기 위한 Web API 사용 시 shadowRoot를 추가해주어야했고, shadowRoot안에 cutom element가 자식요소로 들어가면 부모 요소에서 자식요소인 custom element의 또 다른 자식 요소에 접근할 수 없는 이슈가 발생했습니다.
예시 설명:
Modal
- shadowRoot
- RestaurantInfo
- input
Modal에서 ResturantInfo의 input에 접근하지 못함
shadowRoot안에 있는 custom element의 자식 요소를 custom element의 외부에서 shadowRoot를 통해서도 접근하지 못하는 이유로 shadowRoot와custom element 모두 캡슐화를 진행되고 두 번의 캡슐화로 인해 접근하지 못하는 것 같다는 생각을 했습니다.
리뷰어에게 음식점 리스트의 재사용성에 대해 더 고민해보라는 코드 리뷰를 받았고 custom element로 이를 해결하려 했지만 다음의 한계점들을 느끼게 되었고 custom element를 활용한 기존의 방식을 버리고 관련 레이아웃,기능을 담은 element를 생성하는 클래스를 만들고 이를 상속해서 음식점 리스트외의 다른 요소나 기능이 필요한 컴포넌트를 생성할 수 있도록 진행했습니다. (ex: 음식점 리스트 + 검색 기능)
음식점 리스트 관련 custom element의 한계점
attribute로 리스트 관련 속성들을 불러와서 이용할 수 있지만, 리스트의 대상(모든 음식점,자주 가는 음식점), 변경된 필터링 및 정렬 옵션등 기존의 attribute도 많고 확장성을 생각하면 다루어야할 attribute가 많아진다.
단순히 리스트만 보여주는 요소에서는 필터링,정렬 기능이 필요가 없는데 이것을 만약 List라는 컴포넌트를 만든다면 List컴포넌트의 메서드로 정할 필요는 없고, 해당 기능이 필요한 컴포넌트에서 이를 수행하는 것이 맞다.
보다 많이 재사용할 수 있고 List컴포넌트가 음식점 목록을 보여주는 기능에 집중하게 하려면 음식점 목록의 렌더링,재렌더링이 필요한 곳에서 불러야하는 음식점 목록을 props로 넣어주면 좋은데, 이를 attribute로 하면 태그가 데이터를 가지고 있게 되며 세션 스토리지를 활용해 불러야하는 음식점 목록을 넣어주고 이를 사용하는 방법도 있지만 세션을 관리하는 추가 기능이 필요하다.
이번 미션을 수행하는 내내 기능이 한정되고 변화 가능성이 없는 우테코 미션내에서의 컴포넌트의 재사용성 범위에 대해 고민을 했습니다.
범용적으로 사용할 수 있도록 기능이나 속성들을 덜 특정/한정 지을 수록 컴포넌트의 재사용성이 더 좋다고 알고 있었는데, 그러면 관리해야하는 파일이나 폴더가 많아지게 되고 우테코 미션의 사이즈에 비해서 파일이나 폴더의 개수가 커지는 것은 아닐까? 기간 내에 미션 구현이 가능할까? 이게 오버엔지어링이 아닐까?하는 생각이 들었습니다.
이와 관련해 리뷰어에게 질문을 했고, 좋은 답변을 받게 되었어요.
🗨️ 👤 오버엔지니어링은 말 그대로 미래를 대비해서 과하게 공수를 들인다거나 하는 걸 의미하는데, 굳이 확장 가능성을 일부러 닫을 필요는 없다는 얘기에요.
🗨️ 👤재사용성을 챙기는데, 미션 진행에 방해가 될 수 있으니 어느 선까지만 지킨다 라는 생각은 뭔가 지금 방향이 잘못되어있다는 뜻인 것 같습니다.
🗨️ 👤 재사용성 또한 결국에는 구현 및 유지보수의 편리함을 위해 추구하는 개념이니, 구현하다가 재사용의 필요성을 느끼면 그때 해주면 되고 재사용성을 챙기다 보면 오히려 관리해야하는 파일과 폴더의 수가 줄어들고 코드가 명확해지면서 관리하기가 쉬워질겁니다.
재사용성도 결국에는 코드를 쉽게 이해하고 유지 보수하기 쉬운 방향으로 나아가는 하나의 방법이기 때문에 우선 순위는 "누구나 이 코드를 쉽게 이해할 수 있는가, 이를 보다 쉽게 이용할 수 있는가"것을 중점으로 두기로 했습니다.😊
또한 컴포넌트의 재사용성의 범위를 정할때 만약 해당 버튼이 더 늘어난다면? 만약 음식 리스트가 또 필요하다면?이라는 가정을 하면서 구현해보기로 했습니다.
이전에 vitest로 E2E테스트를 진행한 적이 있었는데, 그때와 비교해보면 cypress가 더 편리했습니다.
vites보다 관련된 자료들이 많고, 오류가 났을 때 어디서 오류가 났는지 오류 해결을 위한 방법도 cypress내에서 추천해주어서 처음 사용해보지만 나름 편리했습니다.
미션 기능 시, "점심 뭐 먹지?"가 단순히 음식점을 메모하는 기능뿐만아니라 사용자에게 정보 전달의 목적도 있다고 생각해 기본 데이터를 넣어주었는데, 만약 기본 데이터를 넣어주지 않았다면 cypress의 fixture라는 기능을 사용해봐도 좋을 것 같습니다.
🐳 도메인 로직에 대한 프로그램 내의 동작에 대한 테스트라면 단위 테스트를, UI와 프로그램 내의 동작에 대한 테스트라면 E2E 테스트를 하자!
단위 테스트는 도메인 로직에 대한 테스트로 주요한 기능을 위주로 그에 대한 테스트 했다면 E2E테스트는 사용자 중심에서 기능이 구현 의도대로 동작하는 지 확인하는 테스트 입니다.
이번에 E2E테스트를 구현하면서 사용자의 UI 조작과 그로 인한 액션도 포함시켜야 하고 다양한 사용자의 사용 패턴에 대해서도 고민해봐야한다는 것을 느꼈습니다.
둘 다 타입을 좁혀가는 방법이지만 적용 방식,사용목적과 키워드에서 차이가 있습니다.
차이점 | 타입 내로잉 | 타입 가이드 |
---|---|---|
적용 방식 | 사용자가 명시할 필요 없이 자동으로 됨 | 사용자가 타입을 명시해주어야 함 |
사용 목적 | 동적으로 타입이 변하는 상황에서 안전하게 타입을 좁혀나갈때 사용 | 특정 조건 하에 변수의 타입을 명확하게 구분하기 위해 사용 |
키워드 | 특별한 키워드가 필요하지 않음 | is 키워드를 주로 사용 |
function checkType(x: number | string) {
if (typeof x === 'string') {
return x.includes("this is string");
} else {
return x.toFixed(7);
}
}
const isInputElement = (target: EventTarget | null): target is Element => {
return target instanceof HTMLInputElement;
};
});
컴파일 타임 | 런타임 | |
---|---|---|
정의 | 소스 코드가 실행 가능한 코드로 변환되는 시점 | 컴파일된 프로그램이 사용자 혹은 시스템에 의해 실제로 실행되고 있는 시점 |
특징 |
|
|
에러의 발생 시점,종류 | 소스 코드 컴파일 하는 도중에 발견, 주로 코드의 문법 오류 | 프로그램 실행 도중에 에러 발생, 입력값 오류,메모리 접근등 에러 이유가 다양 |
타입스크립트의 주요 장점 중 하나는 컴파일 타임에 코드의 안정성을 확보하는 것으로, 오류를 컴파일 타임에서 발견할 수 있으며 더 빨리는 코드를 짤 때 확인할 수 있다는 장점이 있습니다.
우선은 웹컴포넌트를 사용해본 좋은 경험이였습니다. 직접 부딪히면서 장단점을 배워보았다는 것에 큰 의의를 두고 있습니다. 😊(다음 미션때는 안 사용할거에요 ㅎㅎㅎㅎ 이번 미션으로 충분해요.ㅎㅎㅎ)
또한 코드를 구현하는 방법,과정과 해당 코드에 대한 리뷰를 받아들이는 자세에 대해 많이 생각하는 미션이였습니다.
이번 미션을 진행하고 리뷰를 받으면서 다음의 것들을 느끼고 다짐했습니다.
이렇게 또 한번의 우테코 미션을 끝냈습니다. 🥳💫
새로운 것을 해보고 고민하고 좌절도 해보며 깊은 생각을 할 수 있었던 시간이였습니다.
다음 미션은 보다 더 잘 구현할 수 있기를 바라며,이번 회고를 여기서 마치겠습니다. 🖐️😊🖐️
➕ 우테코 근황
드디어 우테코 출입증이 나왔어요!!!!