Intent
Intent는 사용자 액션에서 하나 이상의 Command로의 선언적 매핑이에요. UI에서 호출하는 것 — Hurum 데이터 흐름의 진입점이에요.
import { Intents, Intent } from '@hurum/core'
const PurchaseIntents = Intents('Purchase', { submitButtonClicked: Intent(ValidateCommand, SavePurchaseCommand), pageOpened: Intent(LoadPurchaseCommand), cancelClicked: Intent(CancelCommand),})
// Send from your UIPurchaseStore.send.submitButtonClicked({ id: '123' })동작 방식
섹션 제목: “동작 방식”Intent(Command1, Command2, ...)는 순차 Intent를 생성해요. 트리거되면 각 Command의 Executor를 순서대로 실행해요. 하나가 throw하면 나머지는 건너뛰어요.
Intents()는 Events()가 Event를 그룹화하는 것과 비슷하게, 관련된 Intent를 네임스페이스로 그룹화해요. 각 키는 store.send의 메서드가 돼요:
const CounterIntents = Intents('Counter', { plusClicked: Intent(IncrementCommand), minusClicked: Intent(DecrementCommand), resetClicked: Intent(ResetCommand),})
// After registering with a Store:CounterStore.send.plusClicked({ amount: 1 })CounterStore.send.resetClicked({})send에 전달하는 페이로드는 Intent의 모든 Command에 전달돼요. TypeScript가 컴파일 시점에 페이로드 타입이 모든 Command와 호환되는지 확인해요.
실행 전략
섹션 제목: “실행 전략”Hurum은 여러 Command를 실행하기 위한 세 가지 전략을 제공해요:
const AppIntents = Intents('App', { // Sequential (default) -- run one after another, stop on first error submit: Intent(ValidateCommand, SaveCommand),
// Parallel fail-fast -- run all at once, abort others on first error quickSubmit: Intent.all(ValidateCommand, SaveCommand),
// Parallel independent -- run all at once, each completes regardless initialize: Intent.allSettled( LoadCurrencyCommand, LoadVATCommand, LoadPurchaseCommand, ),})| 전략 | 실행 방식 | 에러 발생 시 |
|---|---|---|
Intent(A, B, C) | A, 그 다음 B, 그 다음 C | 중단. B와 C는 실행되지 않음. |
Intent.all(A, B, C) | A, B, C 동시 실행 | 나머지를 중단. |
Intent.allSettled(A, B, C) | A, B, C 동시 실행 | 나머지는 계속 실행. |
Intent는 자동 및 수동 취소를 지원해요:
재실행 시 자동 취소. 같은 Intent를 다시 보내면 이전 실행을 중단해요. 입력하면서 검색하는 기능에 유용해요: 각 키 입력이 이전 검색을 취소해요.
// The first call starts runningPurchaseStore.send.submitButtonClicked({ id: '123' })
// This aborts the first call and starts a new onePurchaseStore.send.submitButtonClicked({ id: '456' })수동 취소. 특정 Intent에는 store.cancel(ref)을, 모든 것에는 store.cancelAll()을 사용하세요:
// Cancel a specific intentconst ref = PurchaseStore.send.submitButtonClicked({ id: '123' })PurchaseStore.cancel(ref)
// Cancel all running executorsPurchaseStore.cancelAll()Executor가 취소되면 signal.aborted가 true가 되고, 그 이후에 emit된 Event는 조용히 무시돼요.
자주 사용되는 패턴
섹션 제목: “자주 사용되는 패턴”페이지 라이프사이클 Intent
섹션 제목: “페이지 라이프사이클 Intent”const PageIntents = Intents('ProductPage', { // Load everything the page needs opened: Intent.allSettled( LoadProductCommand, LoadReviewsCommand, LoadRecommendationsCommand, ),
// Clean up on leave closed: Intent(CleanupCommand),})폼 제출
섹션 제목: “폼 제출”const FormIntents = Intents('ContactForm', { // Validate first, then submit submitted: Intent(ValidateFormCommand, SubmitFormCommand),
// Individual field changes fieldChanged: Intent(UpdateFieldCommand),})디바운스가 있는 검색
섹션 제목: “디바운스가 있는 검색”자동 취소 동작 덕분에 기본적인 경우에는 별도의 디바운스 유틸리티가 필요 없어요:
const SearchIntents = Intents('Search', { queryChanged: Intent(SearchCommand),})
// Each call cancels the previous oneSearchStore.send.queryChanged({ query: 'h' })SearchStore.send.queryChanged({ query: 'hu' })SearchStore.send.queryChanged({ query: 'hur' }) // only this one completes진정한 디바운스(실행 전 N ms 대기)가 필요하다면, Executor 자체에서 처리하세요.
- Intent 이름은 사용자 액션으로 지정하세요.
plusClicked,formSubmitted,pageOpened—incrementCounter나saveData가 아니에요. Intent는 사용자가 한 일을 설명하는 거지, 시스템이 해야 할 일을 설명하는 게 아니에요. - 독립적인 로드에는
Intent.allSettled를 사용하세요. 통화, VAT 세율, 구매 데이터를 로드하는 것이 독립적이라면, 하나의 실패가 나머지를 차단하지 않게 하세요. - 사용자 액션당 하나의 Intent. 버튼 클릭이 유효성 검사 후 저장을 해야 한다면, 두 개의 Command를 가진 하나의 Intent이지, 두 개의 Intent가 아니에요.
- Intent는 정확히 하나의 Store에 속해요. Store 빌더에서
.intents()로 Intent를 등록해요. 같은 Intent 네임스페이스를 여러 Store에서 공유할 수 없어요.