Va.RadioGroup — 여러 라디오를 배타 선택으로 관리하는 그룹
여러 옵션 중 하나만 선택되도록 자동 관리하는 컴포넌트. 각 Radio를 하드코딩하지 않고 데이터 배열로 선언해 한 번에 렌더링합니다. 값은 선택된 하나의 key로 다룹니다. CheckboxGroup과 완전히 대칭 구조지만 반환값이 배열이 아니라 단일 값이라는 게 결정적 차이.
- 클래스: Va.RadioGroup — va_component.js:11106
- short name: radioGroup
- 상속: Va.Component (PureField 아님)
- isContainer: true
- 베이스 CSS: va-radio-group
1. 기본 사용
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
data: [
{ code: 'M', name: '남성', checked: true },
{ code: 'F', name: '여성' }
],
onChange: 'onGenderChange'
}
getValue() 결과: 'M' — 선택된 항목의 key 단일 값.

2. Va.Radio를 여러 개 나열하는 것과의 결정적 차이
Radio 단독을 여러 개 나열하는 방식과 RadioGroup을 쓰는 차이를 잠깐 짚고 갑니다.
✕ Radio 단독 나열
{
tagName: 'div',
tags: [
{ tagName: 'radio', radioLabel: '남', ref: 'male' },
{ tagName: 'radio', radioLabel: '여', ref: 'female' }
]
}
- 배타 로직 없음 — 둘 다 체크될 수 있음
- 값 조회할 때 각각 getRef 필요
- 서버 응답 동적 목록에 대응 못함
✓ RadioGroup
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
data: [ ... ]
}
- 자동 배타 선택 — setValue가 다른 항목 자동 해제
- getValue() 한 번에 선택 결과
- setData() 로 서버 응답 그대로 반영
3. RadioGroup vs CheckboxGroup vs 다른 그룹
항목Va.CheckboxGroupVa.RadioGroupVa.Combobox
| 선택 개수 | N개 (다중) | 1개 (배타) | 1개 (기본) 또는 N개 |
| getValue() 반환 | key 배열 | 단일 key (null 가능) | 단일 key 또는 배열 |
| setValue() 인자 | key 배열 | 단일 key | 단일 값 또는 배열 |
| 데이터 배열로 렌더 | ✓ | ✓ | ✓ |
| 자체 배타 로직 | — | ✓ (setValue가 다른 것 해제) | ✓ |
| UI 형태 | 나열 (H/V) | 나열 (H/V) | 드롭다운 |
| 상속 | Component | Component | PureField |
한 줄 요약: "여러 옵션에서 하나 선택 → 결과를 단일 key로 받는 컴포넌트."
4. 주요 속성
데이터 관련
속성기본값설명
| data | — | 옵션 배열. 각 항목 { [key값], [display값], checked } |
| key | 'key' | 값 필드명 |
| display | 'display' | 표시 필드명 |
| fakeData | — | 에디터 모드용 미리보기 데이터 |
배치·상태
속성기본값설명
| direction | 'horizontal' | 'horizontal'(가로) / 'vertical'(세로) |
| readonly | — | 읽기 전용 (모든 자식에 전파) |
| disabled | — | 비활성화 (모든 자식에 전파) |
| size | — | 크기 |
| radioLabelClick | true | 라디오 옆 라벨 클릭으로 선택 가능 |
| stopPropagation | true | 이벤트 버블링 차단 |
5. 데이터 스키마
setData()가 받는 배열의 각 항목:
[
{
code: 'M', // key 필드 (옵션의 'key'로 지정한 이름)
name: '남성', // display 필드 (옵션의 'display'로 지정한 이름)
checked: true, // 초기 체크 상태 (선택 사항)
name: '...', // ⚠️ 주의 - 아래 참조
}
]
⚠️ name 필드 오버로딩 주의 — 코드상 item.name이 없으면 componentId로 자동 세팅되고, 렌더링 시 'name_' + componentId 형태로 HTML name 속성이 부여됩니다 (va_component.js:11169-11170, 11177). 데이터에 name 필드가 사용자 이름 같은 실데이터를 담고 있으면 충돌하니, 필드명을 label, title 등으로 바꾸는 게 안전합니다.
⚠️ HTML name 그룹핑도 자동으로 됨 — 같은 그룹 내 모든 라디오가 같은 name 속성을 갖게 되므로, 표준 HTML 폼 서브밋에서도 배타 선택으로 처리됩니다.
6. 이벤트
이벤트시그니처발생 시점
| change | (component, element, key, evt) | 라디오 선택 시. key가 선택된 항목의 값 |
| click | (component, element, key, evt) | 클릭 시. key가 클릭된 항목의 값 |
| keydown | (component, element, keyCode, evt) | 스페이스로 선택 시 |
| mousedown | (component, element, evt) | 그룹 마우스다운 |
change 콜백 예시
onGenderChange(comp, el, key, evt) {
console.log('선택됨:', key); // 'M' 또는 'F'
// 전체 상태를 다시 조회할 필요 없음 — key 자체가 값
}
⚠️ change와 click 인자에 선택된 값이 직접 들어옴 — CheckboxGroup은 "바뀐 항목의 key"였지만, RadioGroup은 배타 선택이라 그것이 곧 "현재 선택된 값"입니다.
7. 메서드
값 조회·세팅
메서드설명
| getValue() | 선택된 항목의 단일 key 반환. 아무것도 선택 안 됐으면 null |
| setValue(key) | 해당 key를 가진 항목을 선택하고 나머지는 자동 해제 (배타 로직) |
| getDisplay() | 선택된 항목의 display 배열 (실제로는 하나뿐이라 길이 0 또는 1) |
| getDisplayAsText() | display를 한 줄 문자열로 |
⚠️ getDisplay() 반환이 배열임에 주의 — 이름은 단수지만 CheckboxGroup 코드에서 복사되어 배열을 반환합니다. getDisplay()[0]로 접근하거나 getDisplayAsText() 사용 권장.
데이터 관리
메서드설명
| setData(data) | 데이터 전체 교체 |
| getData() | 현재 데이터 반환 |
| addData(item) | 항목 추가 (기본 체크됨 — 배타 로직 상 주의) |
| insertData(item, index) | 특정 위치에 삽입 |
| append(component) | 프로그램적으로 라디오 자식 추가 (자동 name 그룹핑) |
상태
메서드설명
| setDisabled(bool) | 자식 전체 비활성화 |
| setReadOnly(bool) | 자식 전체 읽기 전용 |
⚠️ removeData / modifyData는 없음 — 전체 교체는 setData().
8. 내부 구조
<div elname="element" class="va-radio-group [horizontal|vertical]" field="true">
<div elname="inner" class="radio-group-inner">
<div class="va-radio">
<div class="radio-inner">
<div class="field-wrapper"><input type="radio" name="name_..."><span>◉</span></div>
<label>남성</label>
</div>
</div>
<div class="va-radio">
<div class="radio-inner">
<div class="field-wrapper"><input type="radio" name="name_..."><span>◯</span></div>
<label>여성</label>
</div>
</div>
</div>
</div>
- direction: 'horizontal' — 자식들이 가로 나열
- direction: 'vertical' — 세로 나열
- 모든 자식 라디오가 같은 name 속성 — HTML 표준 배타까지 자동
9. 배타 로직의 정체
RadioGroup의 자동 배타는 두 층에서 처리됩니다:
1) setValue(key) 호출 시 (프로그램적)
setValue(key) {
this.value = key;
let components = this.getChildComponents();
for (let i = 0; i < components.length; i++) {
if ((key + '') === components[i].option[this.key] + '') {
components[i].setChecked(true);
} else {
components[i].setChecked(false); // ← 다른 것들 명시적으로 해제
}
}
}
2) 사용자 클릭·키다운 시
setData()에서 각 자식 Radio의 click·keydown 이벤트에 setValue()를 걸어둡니다 (va_component.js:11187, 11192). 그래서 사용자가 어떤 라디오를 클릭하면 자동으로 그 값으로 setValue()가 호출되어 다른 라디오가 해제됩니다.
정리: 개발자가 신경 쓸 필요 없이 자동으로 배타 선택이 유지됨.
10. 언제 쓰나
RadioGroup이 맞을 때
- 여러 옵션 중 하나만 선택 — 성별, 결제 방식, 등급, 배송 방식
- 항목이 2~5개 정도 (한 화면에 보이는 게 자연스러움)
- 서버에서 받은 동적 목록
- 값이 단일 key로 정리되면 편한 상황
다른 걸 쓸 때
- 라벨·검증 필요 → Va.RadioGroupField
- 여러 개 선택 → Va.CheckboxGroup
- 항목 6개 이상 (드롭다운이 더 자연스러움) → Va.Combobox
- 세그먼트 컨트롤 UI → Va.SegmentedControl
- 툴바 배타 토글 → Va.ToggleButton + Va.ButtonGroup
11. 흔한 조합 예시
// 표준 (하나 선택된 상태로 시작)
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
data: [
{ code: 'CARD', name: '신용카드', checked: true },
{ code: 'BANK', name: '계좌이체' },
{ code: 'PHONE', name: '휴대폰 결제' }
],
direction: 'horizontal',
onChange: 'onPaymentChange'
}
// 세로 배치
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
direction: 'vertical',
data: [
{ code: 'BASIC', name: '기본 요금제 - 월 9,900원' },
{ code: 'STANDARD', name: '스탠다드 - 월 19,900원' },
{ code: 'PREMIUM', name: '프리미엄 - 월 29,900원' }
]
}
// 서버에서 데이터 로드
mounted() {
OptionService.list(this, {}, (view, ok, res) => {
if (ok) view.getRef('opt').setData(res.data.list);
});
}
// 초기값 프로그램적 세팅
this.getRef('opt').setValue('BANK');
// 읽기 전용
{
tagName: 'radioGroup',
key: 'code',
display: 'name',
data: [ ... ],
readonly: true
}
// 값 조회
onSubmit() {
const method = this.getRef('payment').getValue();
if (method == null) {
new Va.Alert({ title: '알림', message: '결제 방식을 선택하세요' }).show(this);
return;
}
// 서버 전송
}
12. 실전 예 — 결제 방식 선택
class PaymentForm extends Va.View {
async mounted() {
// 사용자가 사용 가능한 결제 방식 로드
const res = await PaymentService.getMethods(this);
if (res.result) {
this.getRef('method').setData(res.data.list);
// 이전에 사용한 방식을 기본 선택
if (res.data.lastUsed) {
this.getRef('method').setValue(res.data.lastUsed);
}
}
}
onMethodChange(comp, el, key, evt) {
// 결제 방식에 따라 추가 폼 보이기/숨기기
if (key === 'CARD') {
this.getRef('cardInfo').setDisplay('flex');
this.getRef('bankInfo').setDisplay('none');
} else if (key === 'BANK') {
this.getRef('cardInfo').setDisplay('none');
this.getRef('bankInfo').setDisplay('flex');
}
}
onSubmit(btn, el, evt) {
const method = this.getRef('method').getValue();
if (method == null) {
new Va.Alert({ title: '알림', message: '결제 방식을 선택하세요' }).show(this);
return;
}
PaymentService.pay(this, {
method,
amount: 50000
}, (view, ok, res) => {
if (ok) new Va.Alert({ title: '완료', message: '결제되었습니다' }).show(view);
});
}
config() {
return {
tagName: 'page',
tags: [{
tagName: 'panel',
tags: [
{ tagName: 'h2', innerHTML: '결제 방식 선택' },
{
tagName: 'radioGroup',
ref: 'method',
key: 'code',
display: 'name',
direction: 'vertical',
onChange: 'onMethodChange'
},
{ tagName: 'div', ref: 'cardInfo', style: { display: 'none' }, tags: [/* 카드 정보 폼 */] },
{ tagName: 'div', ref: 'bankInfo', style: { display: 'none' }, tags: [/* 계좌 정보 폼 */] },
{
tagName: 'button',
text: '결제하기',
appearance: 'primary',
onClick: 'onSubmit'
}
]
}]
};
}
}
흐름:
- mounted()에서 결제 방식 로드 → 이전 사용 이력이 있으면 자동 선택
- 사용자가 변경 → 방식별 추가 폼 자동 전환
- 제출 시 getValue()로 단일 값 획득
13. 알아두면 좋을 주의사항
- getValue()는 단일 key — CheckboxGroup(배열)과 다름. null 반환 가능성 있음.
- setValue(key)가 배타 처리 담당 — 사용자 클릭 시 자동 호출. 프로그램적으로 호출해도 다른 항목 자동 해제.
- getDisplay()는 배열 반환 — 이름과 달리 배열. [0] 접근이나 getDisplayAsText() 사용.
- change/click의 세 번째 인자는 선택된 key — 그대로 사용 가능.
- Va.Component 상속 — PureField 계열 아님.
- addData는 새 항목을 체크된 상태로 추가 — 코드가 checked: true 고정 (va_component.js:11240). 배타 로직 상 기존 선택이 해제될 수 있으니 주의.
- insertData의 위치 로직 미묘 — append 후 insertBefore 병용. 실제 동작 확인 권장.
- removeData/modifyData 없음 — 전체 교체는 setData().
- 데이터 필드명 name 조심 — HTML name 속성으로 자동 세팅됨. 실데이터 필드명은 label/title 등.
- setData()는 자식 전체 제거 후 재렌더 — 기존 선택 상태 사라짐.
- direction CSS 클래스로 반영 — 실제 flex 방향은 프레임워크 CSS.
- 키보드 조작 — 각 라디오는 스페이스로 선택. 화살표 키 그룹 네비는 없음 (표준 라디오 UX 부재).
- HTML name 자동 그룹핑 — 같은 그룹 라디오들이 같은 name 속성. 표준 폼 서브밋에서도 배타.
- focus() 편의 메서드 없음 — 자식 첫 번째에 포커스 주려면 getChildComponents()[0].focus() 우회.
14. radioGroup vs combobox vs segmentedControl 선택 기준
상황추천
| 옵션 2~5개 (성별, 결제방식 등) | radioGroup |
| 옵션 6개 이상 | combobox (드롭다운) |
| 세그먼트 형태 UI (붙어있는 버튼) | segmentedControl |
| 툴바 배타 토글 (bold/italic 계열) | toggleButton + buttonGroup |
| 폼 안 라벨·검증 필요 | radioGroupField |
| 여러 개 선택 | checkboxGroup |
| 모바일 최적화 (하나만 노출) | combobox |
"옵션 2~5개면 라디오, 그 이상이면 콤보" — 기본 원칙.
15. RadioGroup의 진짜 강점
Radio 단독보다 RadioGroup을 반드시 써야 하는 이유:
- 배타 자동 — 개발자가 관리 코드 안 짜도 됨
- 데이터 기반 — 서버 응답을 바로 반영
- 일관된 값 관리 — getValue() 하나로 결과 획득
- 동적 항목 지원 — 옵션이 바뀌어도 즉시 반영
- HTML name 자동 그룹핑 — 표준 폼 서브밋도 커버
Radio를 단독으로 여러 개 나열하는 걸 고민하고 있다면, 90% 이상 RadioGroup으로 대체 가능합니다.
참고
- API 문서 페이지: https://vanillafront.com/docs.html?theme=light#main#apiradiogroup
- 연관: Va.Radio(자식), Va.RadioGroupField(라벨 포함 버전)
'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글
| Slider (슬라이더) (0) | 2026.09.12 |
|---|---|
| RadioGroupField (라디오그룹 필드) (0) | 2026.09.12 |
| RadioField (라디오 필드) (1) | 2026.09.12 |
| Radio (라디오 버튼) (0) | 2026.09.12 |
| CheckboxGroupField (체크박스그룹필드) (0) | 2026.09.12 |