## HTMX의 심장, 트리거(Trigger)와 고급 제어 기술 {#sec-f4f92dc347e1} 지금까지의 글에서는 [[HTMX]]로 서버에 요청을 보내는 기본적인 방법들을 살펴보았습니다. `hx-get`, `hx-post`, `hx-put`, `hx-delete` 같은 속성을 이용하면, 이제 자바스크립트의 `fetch()` 없이도 꽤 많은 Ajax 동작을 구현할 수 있다는 점을 확인했죠. 그런데 HTMX를 쓰다보면 **요청을 보내는 것 자체보다, 언제 보내는지**를 컨트롤 하고 싶어집니다. 이때 등장하는 것이 `hx-trigger`입니다. `hx-get`이나 `hx-post`가 **"무엇을 할지"** 를 정하는 속성이라면, `hx-trigger`는 **"언제 그것을 할지"** 를 정하는 속성입니다. `hx-trigger`를 쓰지 않는다면 HMTX는 단지 버튼을 클릭했을 때만 요청을 보내는 간단한 Ajax 도구에 불과해보일 겁니다. 하지만 HTMX는 이 `hx-trigger`를 사용하고 부터가 만족감이 올라가기 시작할 겁니다. 예를들어 아래와 같은 것들이 JavaScript 코드 없이 가능해집니다. - 입력이 멈춘 뒤에만 검색 요청 보내기 - 짧은 시간 안의 중복 클릭 막기 - 일정 주기로 자동 새로고침하기 - 요소가 화면에 나타났을 때만 로딩하기 - 특정 조건에서만 요청 보내기 - 서로 다른 요청끼리 충돌하지 않도록 우선순위 조절하기 저도 처음엔 "fetch 몇 줄이면 될 걸 굳이? [[HTMX]]를 왜 쓰지" 라고 생각하며 HTMX를 단순한 Ajax 툴로만 보던 제 태도는 `hx-trigger`의 강력한 제어 기능을 경험한 뒤 완전히 바뀌었습니다. 이번 글에서는 HTMX의 진짜 핵심이라 할 수 있는 **트리거와 고급 제어 기술**을 정리해 보겠습니다.  --- ## [[HTMX]]는 단순한 버튼 도구가 아니다 {#sec-31cdd005939f} 처음 HTMX를 접하면 보통 이런 예제부터 시작합니다. ```html
``` 이 정도만 보면 HTMX는 그냥 "버튼 누르면 Ajax 요청 보내주는 도구"처럼 느껴질 수 있습니다. 물론 그것만으로도 충분히 편리합니다. 하지만 그건 HTMX의 일부일 뿐입니다. 진짜 중요한 것은 **요청 자체가 아니라, 그 요청이 발생하는 타이밍과 조건을 HTML에서 선언할 수 있다는 점**입니다. 예를 들어, - 검색창에서 타이핑마다 서버에 요청을 쏘는 것은 최악의 UX입니다. 반대로 입력이 멈춘 뒤 500ms 후에만 요청이 나간다면 훨씬 자연스럽겠죠. - 어떤 버튼은 단순 클릭이 아니라, **Ctrl 키를 누른 상태에서 클릭했을 때만 작동**하게 만들고 싶을 수도 있습니다. - 혹은 스크롤을 내려 특정 요소가 화면에 보일 때, 그제야 데이터를 가져오고 싶을 수도 있습니다. 이런 순간마다 매번 자바스크립트를 직접 쓰기 시작하면 코드가 빠르게 늘어납니다. 반면 HTMX는 이런 제어를 상당 부분 **JavaScript 한 줄 없이 속성 조합만으로 표현**할 수 있습니다. 이 점이 정말 놀라운 점입니다. **C++ 개발자가 Python코드를 처음 보고 "뭐? 타입 선언도 안한다고??" 라고 느낄 때의 충격이 제가 HTMX의 hx-trigger를 처음 봤을 때의 충격과 비슷할지도 모르겠습니다**. --- ## 기본 트리거: click, change, submit {#sec-a730147be101} 가장 먼저 알아둘 것은, [[HTMX]]는 이미 HTML 요소의 기본 동작과 잘 어울리도록 만들어져 있다는 점입니다. 예를 들어 버튼은 기본적으로 `click`, 폼은 기본적으로 `submit`, 입력 요소는 상황에 따라 `change` 같은 이벤트와 자연스럽게 연결됩니다. ```html ``` 이 버튼은 별도로 `hx-trigger="click"`을 적지 않아도, 클릭 시 요청을 보냅니다. 마찬가지로 폼도 그렇습니다. ```html ``` 이 경우에는 폼 제출 시점에 요청이 발생합니다. 즉, HTMX는 아주 기본적인 시나리오에서는 이미 꽤 똑똑하게 동작합니다. 하지만 우리가 관심을 가져야 할 부분은 여기서부터입니다. **기본값을 넘어서, 원하는 시점과 조건을 직접 선언하는 것.** 우리가 하고 싶은 것이 바로 이것이죠. --- ## `hx-trigger`에서 자주 쓰는 표준 이벤트 vs [[HTMX]] 전용 트리거 {#sec-8354ed76dc75} 우선 중요한 것은 아래 표부터 숙지해야합니다. 키포인트는 **브라우저가 원래 제공하는 DOM의 표준 이벤트는 당연히 사용가능 + 몇가지 HTMX 전용 트리거가 있다.** 입니다. | 구분 | 값 | 의미 | 자주 쓰는 상황 | 예시 | | ---------- | ----------------- | ---------------------------- | ----------------------------------- | ---------------------------------------------------------------------- | | 표준 이벤트 | `click` | 클릭 시 요청 | 버튼, 링크, 액션 실행 | `hx-trigger="click"` | | 표준 이벤트 | `input` | 입력값이 바뀔 때마다 요청 | 실시간 검색, 자동완성 | `hx-trigger="input changed delay:500ms"` | | 표준 이벤트 | `change` | 값이 확정되어 바뀌었을 때 요청 | `select`, `checkbox`, blur 이후 입력 반영 | `hx-trigger="change"` | | 표준 이벤트 | `submit` | 폼 제출 시 요청 | form 전송 | `hx-trigger="submit"` | | 표준 이벤트 | `keyup` | 키를 뗐을 때 요청 | 키 입력 기반 검색, 단축 반응 | `hx-trigger="keyup delay:500ms"` | | 표준 이벤트 | `keydown` | 키를 누르는 순간 요청 | 핫키, 키보드 인터랙션 | `hx-trigger="keydown[from:body]"` | | 표준 이벤트 | `mouseup` | 마우스 버튼을 뗐을 때 요청 | 드래그/선택 이후 반응 | `hx-trigger="mouseup"` | | htmx 전용 | `load` | 요소가 로드되자마자 요청 | 지연 로딩, 초기 데이터 채우기 | `hx-trigger="load"` | | htmx 전용 | `revealed` | 요소가 화면에 보일 때 요청 | 무한 스크롤, lazy loading | `hx-trigger="revealed"` | | htmx 전용 | `intersect` | 요소가 뷰포트와 교차할 때 요청 | 더 정밀한 lazy loading, scroll 기반 로딩 | `hx-trigger="intersect once"` | | htmx 전용 문법 | `every 5s` | 일정 주기마다 요청 | polling, 상태 갱신 | `hx-trigger="every 5s"` | | 커스텀 이벤트 | `my-custom-event` | 직접 정의한 이벤트로 요청 | 서버 헤더, JS 연동, 느슨한 이벤트 아키텍처 | `hx-trigger="itemSaved from:body"` | | modifier | `delay:500ms` | 지정한 시간 동안 추가 이벤트가 없을 때만 요청 | 디바운싱, 실시간 검색 최적화 | `hx-trigger="keyup delay:500ms"` | | modifier | `throttle:1s` | 짧은 시간 안의 반복 요청을 제한 | 중복 클릭 방지, 과도한 요청 억제 | `hx-trigger="click throttle:1s"` | | modifier | `once` | 한 번만 트리거되도록 제한 | 최초 로드, 1회성 이벤트 | `hx-trigger="intersect once"` | | modifier | `changed` | 값이 실제로 바뀐 경우에만 요청 | 입력 필드 최적화, 불필요한 요청 방지 | `hx-trigger="input changed delay:500ms"` | | modifier | `from:body` | 이벤트 감지 대상을 다른 요소로 지정 | 전역 이벤트 수신, 커스텀 이벤트 처리 | `hx-trigger="itemSaved from:body"` | | modifier | `[condition]` | 조건을 만족할 때만 요청 | 보조키 조합, 입력값 길이 조건 | `hx-trigger="click[ctrlKey]"` / `hx-trigger="keyup[value.length > 1]"` | | modifier | `consume` | 부모 등 상위 요소로 이벤트가 전달되지 않도록 소비 | 중첩된 htmx 요청 충돌 방지 | `hx-trigger="click consume"` | | modifier | `queue:first` | 새 이벤트를 큐잉할 때 첫 번째만 유지 | 연속 입력 중 최초 요청만 유지 | `hx-trigger="input queue:first"` | | modifier | `queue:last` | 새 이벤트를 큐잉할 때 마지막만 유지 | 검색창, 자동완성 | `hx-trigger="input queue:last"` | | modifier | `queue:all` | 발생한 이벤트를 모두 큐에 유지 | 모든 이벤트를 순차 처리해야 할 때 | `hx-trigger="input queue:all"` | | modifier | `queue:none` | 진행 중 요청이 있으면 새 이벤트를 무시 | 중복 요청 완전 차단 | `hx-trigger="click queue:none"` | `hx-trigger` 값은 보통 **이벤트(event)** 를 먼저 쓰고, 필요하면 뒤에 **필터(filter)** 와 **modifier** 를 붙여 조합합니다. 즉, **“무슨 일이 발생했을 때(event), 어떤 조건이면(filter), 어떤 방식으로 처리할지(modifier)”** 순서로 읽으면 됩니다. 형태로 보면 보통 `event[filter] modifier modifier` 입니다. ```html ``` - `keyup` → 키를 뗐을 때 - `[value.length > 1]` → 입력값이 2글자 이상일 때만 - `changed delay:500ms` → 값이 바뀌었고, 0.5초 동안 추가 입력이 없을 때만 요청 ## 유용한 예시 몇가지 {#sec-d893c37bc6f7} 위의 표로 정리를 했지만, 여기서 마무리하기는 아쉬우므로 몇 가지 제가 좋아하는 트리거의 예시를 보여드릴까 합니다. ### 입력이 멈춘 뒤에만 요청하기: `delay` {#sec-cd31c7256394} 검색창 자동완성이나 실시간 필터링 같은 기능을 만들 때, 사용자가 타이핑할 때마다 요청을 보내면 서버에도 부담이 가고 사용자 경험도 어딘가 조급해집니다. 이럴 때 `delay`를 사용하면 훨씬 부드러워집니다. ```html ``` 이 코드는 사용자가 키를 누를 때마다 바로 요청하지 않습니다. 대신 **입력이 멈춘 후 500ms가 지나면** 요청을 보냅니다. 이건 사실상 **디바운싱(debouncing)** 입니다. 자바스크립트로 구현하려면 타이머를 잡고, 이전 타이머를 취소하고, 다시 설정하는 식의 코드가 필요합니다. 하지만 [[HTMX]]에서는 그냥 속성으로 끝납니다. 검색, 자동 추천, 필터링 UI에서는 이 기능이 거의 기본이라고 봐도 좋습니다. --- ### 너무 자주 보내지 않기: `throttle` {#sec-c84ae134ee88} `delay`가 "입력이 멈춘 뒤 잠깐 기다렸다가 보낸다"는 느낌이라면, `throttle`은 "짧은 시간 안에 너무 자주 보내는 것을 제한한다"는 쪽에 가깝습니다. ```html ``` 이 경우 사용자가 버튼을 아주 빠르게 여러 번 눌러도, 1초 안에는 과도하게 요청이 연속해서 나가지 않도록 제어할 수 있습니다. 다음 같은 상황에서 꽤 유용합니다. - 중복 클릭 방지 - 너무 빠른 반복 요청 차단 - 서버 부하 줄이기 - 실수로 같은 액션을 여러 번 수행하는 것 막기 특히 "좋아요", "저장", "새로고침", "동기화" 같은 버튼에서는 한 번쯤 고려해볼 만합니다. --- ### 일정 주기마다 자동 요청하기: `every` {#sec-0195e4e0d263} [[HTMX]]를 쓰다 보면 의외로 매력적으로 느껴지는 기능이 바로 이 `every`입니다. 특정 영역을 일정 주기마다 서버에서 새로 받아오고 싶을 때, 굳이 별도 폴링 로직을 자바스크립트로 작성하지 않아도 됩니다. ```html