[ 학습 목표 ]
- 어플리케이션을
컴포넌트 단위로 모듈화
하여 개발
UI를 컴포넌트 단위로 생각하고 개발
하는 연습재사용할 수 있는 컴포넌트를 고민
해보기- 웹 UI 환경에서의 테스트 기초
- 사용자 관점에서 중요하다고 생각하는 기능을 스스로 정의하고
E2E 테스트로 검증
해보기- TypeScript의 기본 문법을 익히며 필요성을 경험
3번째 미션이 마무리가 되었다. step1에는 ui도 다주고 로직도 복잡하지 않은데 왜 시간을 더줬지? 라는 안일한 생각을 가졌다. 결국 딱 맞춰서 제출했고 구조도 뭔가 만족스럽지 않았다. 초반에 여유롭게 생각을 해서 그런지 진도가 나가지 않아서 마지막엔 급하게 구현했던 부분이 아쉬웠다.
나는 주차별로 미션을 진행할 때 학습 목표에 꽤나 초점을 두고 진행한다. 이번 학습 목표는 재사용성을 고려하여 컴포넌트를 모듈화
하고, 동적인 부분과 정적인 부분을 분리
하는 것이 목적이였다. 그래서 재사용되지 않는 데이터는 정적리소스로 유지하고, 동적인 부분만 컴포넌트화 시켜 이벤트핸들러를 등록하도록 완전히 분리하였다.
하지만 코드리뷰와 공통 피드백, 다른 크루들의 의견을 들어보면서 이러한 의문이 들었다.
재사용성만이 컴포넌트를 만드는 이유인가?
컴포넌트란 무엇일까? 재사용성을 고려하여 애플리케이션을 분리한 단위
라고 생각한다. 단순히 UI만 분리할 수도 있고, 로직을 갖고 있을 수도 있다. 초반에 컴포넌트 설계대로 추상화 수준을 맞추고, 공통 컴포넌트로 만드려고 설계할수록 컴포넌트 내부에 로직이 적고, 외부에서 주입받는 데이터를 사용한다.
공통 피드백, 유연성 강화, 메타인지 말하기 등에서 여러번 말한 주제인데, 이번 미션 때 가장 고민했던 부분인 것 같다. 위에서 말했듯이 렌더링 비용
에 꽂혀서 동적으로 렌더링해야하는 부분만 모듈화를 시켰었다. 그랬더니 로직이 흩어져있는 기분이 들었고, 이를 하나의 컴포넌트화시키면 가독성
, 응집성
, 유지보수
모두 챙길 수 있을 것 같았다. 현재 데이터가 많지 않으므로, 렌더링 비용을 고려한다는 게 오히려 현재상황에서는 오버엔지니어링이라고 볼 수도 있겠다.
컴포넌트가 재사용할 수 있는 단위라고 해서, 현재 재사용되지 않으니까 컴포넌트로 묶으면 안돼! 이런 건 없는 것 같다. 컴포넌트로 만드는 기준을 상황에 따라 판단하는 능력이 필요하다. 개인적인 생각으로는 파일 이름으로 추상화 수준을 어느정도 표현
할 수 있는 것 같다. Button, Dropdown 등 일반적인 이름일수록 공통 컴포넌트 느낌이 강하다. 하지만 무조건 옳은 이론은 없다. 상황에 따라 다르고, 특정 문제를 해결할 수 있는 적합한 해결 방법이 있다는 사실을 자주 리마인드해야겠다.
컴포넌트 관련 아티클을 읽었는데, 기준이 명확해지고 싶다면 SOLID 원칙을 참고해도 괜찮을 것 같다.
이상적인 소프트웨어 구조란 변경된 요구사항이 전달되었을 때 다른 컴포넌트에 영향(side-effect)이 가지 않도록 딱 원하는 부분만 변경할 수 있는, 응집도가 높고 결합도가 낮은 컴포넌트의 집합 구조
SRP
: SRP 원칙을 지키며 컴포넌트를 설계하는 것은 요구사항을 전달하는 책임 단위로 설계한다고 볼 수 있다. 책임 != 동작
임을 명심하고, SRP의 책임은 소프트웨어 내부의 ‘동작’ 이나 ‘논리’가 아니라 조직 간 커뮤니케이션 영역으로 봐야 한다. 예를 들어, 로그인 컴포넌트가 있을 때 비밀번호 찾기 기능을 추가할 경우 새로운 컴포넌트를 만들어야 한다.
OCP
: 기존 기능을 변경하는 것이 아닌 새로운 함수를 추가하는 것
최대한 데이터의 변화나 로직의 변경에도 유연하게 대처할 수 있는 구조 중요
if, else if 분기문이 반복되고, 기능이 추가될 때마다 if문이 늘어난다면 OCP를 위반했을 수 있다.
LSP
: 부모 클래스의 기능을 자식 클래스는 모두 대체할 수 있어야 함
Button 클래스를 상속받는 SpecialButton이 있다면 Button 기능도 모두 해야함
이벤트가 발생하지 않아도, 이벤트를 강제로 트리거시킬 수 있다 → distpachEvent
데이터를 변경하는 것
과 DOM 트리를 리렌더링하는 것
두가지 별도의 작업이 필요했다. 음식점을 추가하는 경우와 삭제하는 경우, 데이터를 변경하고 리렌더링할 때 카테고리와 정렬을 고려하기 위해서 dropdown의 선택된 값이 필요했다. 그래서 dropdown의 인스턴스를 넘겨 메서드를 호출해야 하나 고민했었는데 dispatchEvent를 통해 이벤트를 트리거할 수 있었다.
예를 들어 dropdown 값에 따라 필터링하는 change 이벤트가 존재한다. 그리고 change 이벤트가 발생하지 않더라도, 해당 dom 객체를 가져와 dispatchEvent 메서드를 실행하면 그 객체에 등록되어있는 이벤트를 트리거할 수 있다. 이를 통해 음식점 추가, 삭제 시에도 현재 음식점 목록을 정렬된 상태로 유지할 수 있었다.
// App.ts
const $categoryFilter = document.getElementById('category-filter');
$categoryFilter.addEventListener('handle', handleChange);
// Modal.ts
dispatchSelectEvent() {
const $categoryFilter = dom.getElement('#category-filter');
const filterEvent = new Event('change', {
bubbles: true,
cancelable: true,
});
$categoryFilter.dispatchEvent(filterEvent);
}
이외에도 CustomEvent
를 활용하면 이벤트 이름을 커스텀할 수 있고, 이벤트가 발생했을 때 이벤트 객체로 원하는 데이터를 넘겨줄 수도 있다. 브라우저에서 만들지 않은 사용자 지정 이벤트 객체는 bubbles
, cancelable
, composed
등 원하는대로 옵션을 줄 수 있다.
브라우저가 발송하는 native 이벤트는 이벤트 루프를 통해 비동기적
으로 handler를 호출하지만, dispatchEvent() 로 발송된 이벤트는 handler를 동기적
으로 호출한다고 한다.
바닐라 자바스크립트로 컴포넌트를 만드는 게 쉽지 않았다. 또 이를 재사용하려고 하다보니 더 복잡해지고, 컴포넌트 생성 기준에 대한 고민도 하다보니 더 오래 걸렸던 것 같다. 다음 미션엔 컴포넌트 설계를 제대로 해놓고 진행하는 것도 좋은 방법인 것 같다. 아자아자 🔥🔥🔥
https://ko.javascript.info/dispatch-events
Event 객체와 CustomEvent
https://fe-developers.kakaoent.com/2023/230330-frontend-solid/
https://yozm.wishket.com/magazine/detail/2479/?utm_source=oneoneone
포스팅 잘읽엇습니다👍👍