17 KiB
17 KiB
액션 핸들러 가이드
TL;DR (5초 요약)
1. 구조: type 또는 event(이벤트), handler(핸들러명), params(옵션)
2. type: 표준 DOM 이벤트 (click, change) → $event.target.value
3. event: 커스텀 콜백 (CodeEditor, DataGrid) → $args[0]
4. 핸들러명 정확히: apiCall (❌ api), navigate (❌ goto)
5. 조합: sequence(순차), parallel(병렬), onSuccess/onError
분리된 문서
이 문서는 가독성을 위해 다음과 같이 분리되었습니다:
| 문서 | 내용 |
|---|---|
| actions.md (현재) | 개요, 액션 정의 구조, 내장 핸들러 목록, 주의사항 |
| actions-handlers.md | 핸들러별 상세 사용법, onSuccess/onError, errorHandling, 실전 예시 |
| actions-g7core-api.md | G7Core.dispatch, 편의 API, 이벤트 헬퍼 |
목차
개요
액션 핸들러는 레이아웃 JSON에서 사용자 인터랙션(클릭, 폼 제출 등)을 처리하는 시스템입니다.
핵심 원칙
중요: 핸들러 이름은 ActionDispatcher에 정의된 이름과 정확히 일치해야 함
✅ 필수: apiCall (❌ api 아님)
✅ 필수: navigate, setState, openModal, closeModal, toast 등
액션 정의 구조
{
"actions": [
{
"type": "click",
"handler": "navigate",
"target": "/path",
"params": {
"key": "value"
},
"onSuccess": [],
"onError": [],
"confirm": "확인 메시지",
"key": "Enter"
}
]
}
필드 설명
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
type |
string | 표준 DOM 이벤트 타입 (click, change, submit 등). event 필드와 상호 배타적 |
|
event |
string | 커스텀 콜백 이벤트명 (onSortChange, onChange 등). type 필드와 상호 배타적 |
|
handler |
string | ✅ | 액션 핸들러 이름 |
target |
string | ❌ | 핸들러별 타겟 값 (URL, 모달 ID 등) |
params |
object | ❌ | 핸들러에 전달할 파라미터 |
onSuccess |
array | ❌ | 성공 시 실행할 후속 액션 (단일 또는 배열) |
onError |
array | ❌ | 실패 시 실행할 후속 액션 (단일 또는 배열) |
confirm |
string | ❌ | 실행 전 확인 대화상자 메시지 |
key |
string | ❌ | 키보드 이벤트 필터 (Enter, Escape 등) |
cases |
object | ❌ | switch 핸들러용 케이스별 액션 정의 |
if |
string | ❌ | 조건부 실행 ({{expression}} 형식, truthy일 때만 실행) |
debounce |
number | object | ❌ | 연속 호출 제어 (300 또는 { delay, leading, trailing }) |
render |
boolean | ❌ | false이면 상태 값만 저장하고 React 리렌더를 건너뜀 (engine-v1.42.0+). 기본: true |
resultTo |
object | ❌ | 핸들러 실행 결과를 상태에 저장 (engine-v1.11.0+). { target, key, merge } |
actionRef |
string | ❌ | named_actions에 정의된 액션을 참조 (engine-v1.19.0+). handler/params 대신 사용 |
참고:
type과event중 하나는 반드시 필요.type은 표준 DOM 이벤트용,event는 커스텀 콜백용
actionRef로 named_actions 참조 (engine-v1.19.0+)
named_actions에 정의된 재사용 가능한 액션을 actionRef로 참조합니다. 동일한 액션이 여러 컴포넌트에서 반복될 때 중복을 제거합니다.
// 레이아웃 최상위에 named_actions 정의
{
"named_actions": {
"searchProducts": {
"handler": "navigate",
"params": {
"path": "/admin/products",
"replace": true,
"mergeQuery": true,
"query": { "page": 1, "search_keyword": "{{$computed.keyword || null}}" }
}
}
},
"components": [
{
"name": "Button",
"actions": [{ "type": "click", "actionRef": "searchProducts" }]
},
{
"name": "Input",
"actions": [{ "type": "keypress", "key": "Enter", "actionRef": "searchProducts" }]
}
]
}
규칙:
actionRef사용 시handler/params/target은 생략 가능 (named_actions 정의에서 가져옴)type,key,event등 이벤트 바인딩 속성은 인라인에서 지정- 인라인에
handler/params를 함께 지정하면 named_actions 값을 오버라이드 named_actions는 Partial 파일이 아닌 부모 레이아웃에 정의- 상속(extends) 시 자식 레이아웃의
named_actions가 부모를 오버라이드
resultTo로 핸들러 결과 저장 (engine-v1.11.0+)
핸들러 실행 결과를 상태(_local, _global, _isolated)에 저장합니다. 커스텀 핸들러가 반환한 값을 레이아웃에서 활용할 때 사용합니다.
{
"handler": "sirsoft-ecommerce.buildProductColumns",
"params": {
"baseColumns": [],
"currencies": "{{ecommerceSettings?.data?.language_currency?.currencies}}"
},
"resultTo": {
"target": "_local",
"key": "productColumns"
}
}
resultTo 필드
| 필드 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
target |
string | ✅ | - | 저장 대상: "_local", "_global", "_isolated" |
key |
string | ✅ | - | 상태 키 (dot notation 지원, {{}} 동적 경로 지원) |
merge |
string | ❌ | "deep" |
병합 모드: "replace", "shallow", "deep" |
병합 모드
| 모드 | 설명 |
|---|---|
replace |
기존 값을 완전히 교체 |
shallow |
1단계만 병합 ({ ...existing, ...new }) |
deep |
중첩 객체까지 재귀 병합 (기본값) |
동적 key 예시
{
"handler": "sirsoft-ecommerce.calculatePrice",
"params": { "optionId": "{{option.id}}" },
"resultTo": {
"target": "_local",
"key": "calculatedPrices.{{option.id}}",
"merge": "shallow"
}
}
주의사항
resultTo는 핸들러가 값을 반환(return)해야 동작 — undefined 반환 시 무시됨
init_actions에서 _local 저장 시 globalStateUpdater 경유 (componentContext 없는 환경)
✅ onSuccess/onError와 독립적으로 동작 — resultTo 저장 후 onSuccess 실행
이벤트 타입
| type | 설명 | 사용 컴포넌트 |
|---|---|---|
click |
클릭 이벤트 | Button, Link, 모든 요소 |
change |
값 변경 이벤트 | Input, Select, Checkbox |
submit |
폼 제출 이벤트 | Form |
keydown |
키보드 누름 이벤트 | Input, 모든 요소 |
keyup |
키보드 뗌 이벤트 | Input, 모든 요소 |
focus |
포커스 획득 | Input, Select |
blur |
포커스 해제 | Input, Select |
scroll |
스크롤 이벤트 | 스크롤 가능한 요소 (Div 등) |
mount |
컴포넌트 마운트 | 모든 요소 |
unmount |
컴포넌트 언마운트 | 모든 요소 |
onSortEnd |
sortable 정렬 완료 | sortable 컴포넌트 |
onSortStart |
sortable 드래그 시작 | sortable 컴포넌트 |
참고: HTML5 네이티브 드래그 이벤트(
dragstart,dragover,drop등)도 지원되지만, 목록 정렬에는sortable속성 +onSortEnd이벤트 사용을 권장합니다. 상세: actions-handlers-state.md
커스텀 이벤트 (event 필드)
표준 DOM 이벤트가 아닌 컴포넌트 콜백을 처리할 때 사용합니다.
중요: 컴포넌트가 DOM Event 객체가 아닌 값(문자열, 배열 등)을 직접 전달하는 경우
type 필드 대신 event 필드를 사용하고, $args로 인자에 접근해야 함
| 방식 | 필드 | 데이터 접근 | 사용 상황 |
|---|---|---|---|
| 표준 DOM 이벤트 | type: "change" |
{{$event.target.value}} |
Input, Select, Checkbox 등 |
| 커스텀 콜백 | event: "onChange" |
{{$args[0]}} |
CodeEditor, DataGrid 등 |
예시: CodeEditor onChange 처리
// ✅ 올바름: CodeEditor는 string을 직접 전달
{
"type": "composite",
"name": "CodeEditor",
"props": {
"value": "{{_global.editorContent}}"
},
"actions": [
{
"event": "onChange",
"handler": "setState",
"params": {
"target": "global",
"editorContent": "{{$args[0]}}"
}
}
]
}
// ❌ 잘못됨: type: "change"는 DOM Event 객체를 기대함
{
"actions": [
{
"type": "change",
"handler": "setState",
"params": {
"editorContent": "{{$event.target.value}}"
}
}
]
}
예시: DataGrid onRowAction 처리
{
"type": "composite",
"name": "DataGrid",
"actions": [
{
"event": "onRowAction",
"handler": "switch",
"params": {
"value": "{{$args[0].actionId}}"
},
"cases": {
"delete": {
"handler": "apiCall",
"target": "/api/items/{{$args[0].row.id}}",
"params": { "method": "DELETE" }
}
}
}
]
}
예시: DataGrid onSelectionChange 처리
// ✅ 올바름: onSelectionChange는 선택된 ID 배열을 직접 전달
{
"type": "composite",
"name": "DataGrid",
"props": {
"selectable": true,
"selectedIds": "{{_local.selectedIds ?? []}}"
},
"actions": [
{
"event": "onSelectionChange",
"handler": "setState",
"params": {
"target": "local",
"selectedIds": "{{$args[0]}}"
}
}
]
}
// ❌ 잘못됨: $event는 DOM Event 객체 → DataGrid가 깨짐 (컴포넌트 로드 실패)
{
"actions": [
{
"event": "onSelectionChange",
"handler": "setState",
"params": {
"selectedIds": "{{$event}}"
}
}
]
}
주요 composite 컴포넌트 콜백 목록
필수: 아래 콜백은 모두 event 필드 + $args[0] 패턴 사용 필수
$event 사용 시 DOM Event 객체가 전달되어 컴포넌트 오류 발생
| 컴포넌트 | 콜백 이벤트 | $args[0] 값 |
|---|---|---|
| DataGrid | onSelectionChange |
선택된 ID 배열 number[] |
| DataGrid | onRowAction |
{ actionId, row } 객체 |
| DataGrid | onSortChange |
{ key, direction } 객체 |
| DataGrid | onPageChange |
페이지 번호 number |
| CategoryTree | onNodeSelect |
선택된 노드 객체 |
| CodeEditor | onChange |
에디터 내용 string |
| HtmlEditor | onChange |
HTML 내용 string |
| FileUploader | onUploadComplete |
업로드 결과 객체 |
| TagInput | onChange |
태그 배열 string[] |
$args 배열 접근
event 필드 사용 시 콜백의 모든 인자가 $args 배열에 저장됩니다:
| 바인딩 | 설명 |
|---|---|
{{$args[0]}} |
첫 번째 인자 |
{{$args[1]}} |
두 번째 인자 |
{{$args[0].field}} |
첫 번째 인자의 field 속성 |
내장 핸들러 목록
| 핸들러 | 설명 | target 용도 | params 용도 |
|---|---|---|---|
navigate |
페이지 이동 | - | path, mergeQuery, query |
navigateBack |
브라우저 뒤로가기 | - | - |
navigateForward |
브라우저 앞으로가기 | - | - |
apiCall |
API 호출 | 엔드포인트 URL | method, body, headers |
login |
로그인 (토큰 저장) | 인증 타입 (admin/user) | body (email, password) |
logout |
로그아웃 | - | - |
setState |
상태 변경 | - | target (global/local), 상태 값들 |
setError |
에러 상태 설정 | 에러 메시지 | - |
openModal |
모달 열기 | 모달 ID | - |
closeModal |
모달 닫기 | - | - |
showAlert |
알림 표시 (alert) | 메시지 | - |
toast |
토스트 알림 표시 | - | type, message |
switch |
조건 분기 처리 | - | params.value, cases (케이스별 액션 정의, default 지원) |
sequence |
순차 액션 실행 | - | actions (액션 배열) |
parallel |
병렬 액션 실행 | - | actions (액션 배열) |
refetchDataSource |
데이터 소스 다시 fetch | - | dataSourceId |
remount |
컴포넌트 강제 리마운트 | - | componentId |
reloadExtensions ⭐ NEW |
확장 상태 원자 재동기화 (routes+translations+layouts) | - | moduleInfo, pluginInfo, action |
reloadRoutes (deprecated) |
라우트 다시 로드 — reloadExtensions 위임 |
- | - |
reloadTranslations (deprecated) |
다국어 파일 다시 로드 — reloadExtensions 위임 |
- | - |
refresh |
현재 페이지 전체 새로고침 | - | - |
stopPropagation |
이벤트 전파 중지 | - | - |
preventDefault |
기본 동작 방지 | - | - |
showErrorPage |
에러 페이지 렌더링 | - | errorCode, target, containerId, layout |
상세 문서: 각 핸들러별 상세 사용법은 actions-handlers.md 참조
Debounce 옵션
연속 호출되는 액션의 실행을 제어하여 성능을 최적화합니다. 텍스트 입력 필드의 실시간 업데이트 등에 유용합니다.
기본 사용법
{
"type": "change",
"handler": "sirsoft-ecommerce.updateOptionField",
"params": {
"productId": "{{row.id}}",
"field": "selling_price",
"value": "{{$event.target.value}}"
},
"debounce": 300
}
상세 설정
{
"type": "change",
"handler": "myHandler",
"debounce": {
"delay": 500,
"leading": false,
"trailing": true
}
}
옵션 설명
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
delay |
number | 300 | 대기 시간 (ms) |
leading |
boolean | false | 첫 호출 즉시 실행 |
trailing |
boolean | true | 마지막 호출 후 실행 |
주의사항
debounce 사용 시 이벤트 객체는 비동기 접근을 위해 추출됩니다
✅ 정상 접근 가능: $event.target.value, $event.target.checked
❌ 사용 불가: $event.preventDefault(), $event.stopPropagation()
사용 시나리오
| 시나리오 | debounce 권장 |
|---|---|
| 가격 입력 필드 | ✅ 300ms |
| 검색 입력 필드 | ✅ 300~500ms |
| 텍스트 입력 필드 | ✅ 300ms |
| Toggle/Checkbox | ❌ 즉시 실행 |
| Select 드롭다운 | ❌ 즉시 실행 |
| 버튼 클릭 | ❌ 즉시 실행 |
주의사항
1. 핸들러 이름 정확히 사용
// ✅ 올바른 핸들러 이름
{ "handler": "apiCall" }
{ "handler": "navigate" }
{ "handler": "setState" }
// ❌ 잘못된 핸들러 이름
{ "handler": "api" }
{ "handler": "nav" }
{ "handler": "set_state" }
2. params vs target 구분
// navigate: params.path 사용
{
"handler": "navigate",
"params": { "path": "/admin/users" }
}
// apiCall: target으로 URL 지정
{
"handler": "apiCall",
"target": "/api/admin/users"
}
// openModal: target으로 모달 ID 지정
{
"handler": "openModal",
"target": "confirm_modal"
}
3. 상태 변경 target 타입
// 전역 상태 변경
{
"handler": "setState",
"params": {
"target": "global",
"selectedIds": []
}
}
// 로컬 상태 변경
{
"handler": "setState",
"params": {
"target": "local",
"isEditing": true
}
}
4. 후속 액션 체인
{
"handler": "apiCall",
"target": "/api/admin/users/{{row.id}}",
"params": { "method": "DELETE" },
"onSuccess": [
{ "handler": "toast", "params": { "type": "success", "message": "삭제되었습니다" } },
{ "handler": "navigate", "params": { "path": "/admin/users" } }
],
"onError": [
{ "handler": "toast", "params": { "type": "error", "message": "삭제 실패" } }
]
}
5. 확인 다이얼로그
{
"handler": "apiCall",
"target": "/api/admin/users/{{row.id}}",
"params": { "method": "DELETE" },
"confirm": "정말 삭제하시겠습니까?"
}
6. 키보드 이벤트 필터링
{
"type": "keydown",
"handler": "navigate",
"params": { "path": "/admin/search" },
"key": "Enter"
}
7. $event 바인딩 시 값 추출
주의: {{$event}}를 직접 사용하면 Event 객체가 전달됩니다
Input/Select: "{{$event.target.value}}" 사용
Checkbox/Toggle: "{{$event.target.checked}}" 사용
❌ 잘못됨: "{{$event}}" → NaN 오류 발생
// ✅ 올바른 예시 - Input/Select
{
"type": "change",
"handler": "setState",
"params": {
"searchQuery": "{{$event.target.value}}"
}
}
// ✅ 올바른 예시 - Checkbox/Toggle
{
"type": "change",
"handler": "setState",
"params": {
"isActive": "{{$event.target.checked}}"
}
}
// ❌ 잘못된 예시 - NaN 오류 발생
{
"type": "change",
"handler": "myHandler",
"params": {
"value": "{{$event}}"
}
}
주의: {{$event}}를 숫자 필드에 사용하면 parseFloat(eventObject)가 NaN을 반환하여 0으로 처리됩니다.
관련 문서
- 핸들러 상세 사용법 - 각 핸들러별 파라미터, onSuccess/onError, errorHandling
- G7Core API - React 컴포넌트에서 액션 실행
- 레이아웃 JSON 스키마 - 전체 레이아웃 구조
- 데이터 바인딩 -
{{}}표현식,$t:다국어 - 상태 관리 -
_global전역 상태