커스텀 엘리먼트: Rails에서 Stimulus 대신 활용하는 방법

Building optimistic UI in Rails (and learn custom elements)

작성자
HackerNews
발행일
2025년 12월 04일

핵심 요약

  • 1 커스텀 엘리먼트는 웹 플랫폼의 기본 기능으로, `turbo-frame` 및 `turbo-stream`과 같이 JavaScript 동작이 연결된 자신만의 HTML 태그를 정의할 수 있게 합니다.
  • 2 Stimulus 컨트롤러와 비교하여 재사용 가능한 컴포넌트에 더 적합하며, `connectedCallback` 및 `attributeChangedCallback` 등을 통해 브라우저 자체적으로 동작을 제어합니다.
  • 3 간단한 카운터부터 서버 응답 대기 없이 즉시 UI를 업데이트하는 낙관적(optimistic) 폼 구현까지, Rails 및 Turbo Streams와 연동하여 동적인 웹 애플리케이션 개발에 활용될 수 있습니다.

도입

커스텀 엘리먼트는 복잡하게 들리지만 실제로는 매우 간단한 웹 플랫폼 기능입니다. Rails에서 Hotwire를 사용해 본 경험이 있다면, 이미 `<turbo-frame>`과 `<turbo-stream>` 같은 커스텀 엘리먼트를 접해본 것입니다. 이는 JavaScript 동작이 연결된 단순한 HTML 태그에 불과합니다. 본 문서는 커스텀 엘리먼트의 개념, Stimulus 컨트롤러와의 비교, 그리고 직접 커스텀 엘리먼트를 구축하는 방법을 심층적으로 다룹니다. 간단한 카운터 예시부터 시작하여, 서버 응답을 기다리지 않고 즉시 업데이트되는 낙관적(optimistic) 폼 구현까지 살펴보며, Rails 개발자에게 유용한 웹 개발 도구로서의 가치를 조명합니다.

커스텀 엘리먼트란?

  • 정의: 자신만의 HTML 태그와 커스텀 동작을 정의할 수 있게 해주는 웹 컴포넌트 표준의 일부입니다. Shadow DOM, 템플릿과 함께 사용되지만 독립적으로도 활용 가능합니다.

  • 구현: HTMLElement를 확장하는 클래스를 정의하고, customElements.define() 메서드를 사용하여 브라우저에 등록합니다.

  • 생명주기:
    • connectedCallback(): 엘리먼트가 페이지에 추가될 때 실행됩니다 (Stimulus의 connect()와 유사).
    • disconnectedCallback(): 엘리먼트가 페이지에서 제거될 때 실행됩니다.
  • 명명 규칙: 기존 HTML 엘리먼트와의 충돌을 방지하기 위해 반드시 하이픈(-)을 포함해야 합니다 (예: <hello-world>).

  • 속성 및 변경 감지:
    • getAttribute(): 일반 HTML 엘리먼트처럼 속성 값을 읽을 수 있습니다.
    • static observedAttributes: 감시할 속성 목록을 정의합니다.
    • attributeChangedCallback(): observedAttributes에 등록된 속성 값이 변경될 때 호출됩니다.
  • is 속성: extends 옵션을 사용하여 기존 HTML 엘리먼트(예: <button>)를 확장할 수 있으나, Safari 지원 문제로 인해 권장되지 않습니다.

커스텀 엘리먼트 vs. Stimulus

  • 유사점: connectedCallback()connect()처럼 생명주기 메서드가 유사합니다.

  • 차이점:
    • 요소 찾기: Stimulus는 data-target 속성을 사용하지만, 커스텀 엘리먼트는 querySelector()와 같은 표준 DOM 메서드를 활용합니다.
    • 상태 관리: Stimulus는 data-value를 사용하지만, 커스텀 엘리먼트는 속성(attribute)과 프로퍼티(property)를 사용합니다.
    • 이벤트 처리: Stimulus는 data-action 속성을 사용하지만, 커스텀 엘리먼트는 addEventListener()를 활용합니다.
    • 의존성: Stimulus는 프레임워크가 필요하지만, 커스텀 엘리먼트는 브라우저 자체 기능입니다.
  • 활용: Stimulus는 기존 HTML에 동작을 연결하는 데 좋고, 커스텀 엘리먼트는 재사용 가능한 독립적인 컴포넌트를 만들 때 유리합니다.

간단한 카운터 구축

  • 구현: ClickCounter 클래스를 정의하고 connectedCallback에서 count를 초기화하며 클릭 이벤트 리스너를 추가합니다. increment 메서드에서 count를 증가시키고 querySelector를 이용해 span 태그의 텍스트를 업데이트합니다.

  • Rails 연동: app/javascript/components 디렉토리에 파일을 생성하고 application.js에서 임포트합니다. config/importmap.rb에 해당 디렉토리를 핀(pin)하여 사용할 수 있도록 설정합니다.

  • 사용: <click-counter> 태그를 뷰에 추가하여 즉시 동작하는 카운터를 구현합니다.

낙관적(Optimistic) 폼 구축

  • 목표: 서버 응답을 기다리지 않고 메시지를 즉시 화면에 추가하고, 이후 서버 응답(Turbo Stream)으로 실제 메시지로 교체합니다.

  • HTML 구조:
    • <optimistic-form>: 커스텀 엘리먼트 래퍼.
    • <form>: 메시지 전송 폼.
    • <template response>: 서버 응답 시 메시지를 렌더링할 HTML 구조를 담는 템플릿. cloneNode(true)로 복사하여 사용합니다.
  • JavaScript 구현:
    • OptimisticForm 클래스에서 폼, 템플릿, 타겟 엘리먼트를 연결합니다.
    • #submit(): 폼 유효성 검사 후 FormData를 추출하여 #render() 메서드로 낙관적 UI를 생성하고 target에 추가합니다.
    • #render(formData): 템플릿 내용을 복사하고, formData의 값으로 data-field 속성을 가진 엘리먼트의 textContent를 업데이트합니다.
    • #reset(): turbo:submit-end 이벤트 발생 시 폼을 초기화합니다.
  • Rails Turbo Stream 연동: create.turbo_stream.erb에서 turbo_stream.replace "optimistic-message", @message를 사용하여 낙관적 메시지를 실제 데이터베이스 메시지로 교체합니다. 이로써 사용자 경험을 향상시키고 로딩 스피너 없이 즉각적인 피드백을 제공합니다.

결론

커스텀 엘리먼트는 Rails 개발자에게 강력하고 재사용 가능한 웹 컴포넌트 구축 도구를 제공합니다. 특히 낙관적 UI 패턴과 같이 즉각적인 사용자 피드백이 필요한 채팅 메시지, 댓글, 할 일 목록 등의 기능 구현에 매우 효과적입니다. 템플릿 엘리먼트 내부에 부분 뷰(partial)를 직접 포함함으로써 JavaScript에서 HTML을 중복 작성할 필요 없이, 부분 뷰 변경 시 낙관적 UI도 자동으로 업데이트되는 장점이 있습니다. 이처럼 커스텀 엘리먼트는 Rails 애플리케이션에 쉽게 통합되어 개발 생산성을 높이고, 사용자 경험을 개선하는 또 하나의 유용한 도구로 활용될 수 있습니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!