컴포넌트/필드 컴포넌트

RadioGroup (라디오 그룹)

VanillaFront 2026. 9. 12. 19:55

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'
                    }
                ]
            }]
        };
    }
}

흐름:

  1. mounted()에서 결제 방식 로드 → 이전 사용 이력이 있으면 자동 선택
  2. 사용자가 변경 → 방식별 추가 폼 자동 전환
  3. 제출 시 getValue()로 단일 값 획득

13. 알아두면 좋을 주의사항

  1. getValue()는 단일 key — CheckboxGroup(배열)과 다름. null 반환 가능성 있음.
  2. setValue(key)가 배타 처리 담당 — 사용자 클릭 시 자동 호출. 프로그램적으로 호출해도 다른 항목 자동 해제.
  3. getDisplay()는 배열 반환 — 이름과 달리 배열. [0] 접근이나 getDisplayAsText() 사용.
  4. change/click의 세 번째 인자는 선택된 key — 그대로 사용 가능.
  5. Va.Component 상속 — PureField 계열 아님.
  6. addData는 새 항목을 체크된 상태로 추가 — 코드가 checked: true 고정 (va_component.js:11240). 배타 로직 상 기존 선택이 해제될 수 있으니 주의.
  7. insertData의 위치 로직 미묘  append  insertBefore 병용. 실제 동작 확인 권장.
  8. removeData/modifyData 없음 — 전체 교체는 setData().
  9. 데이터 필드명 name 조심 — HTML name 속성으로 자동 세팅됨. 실데이터 필드명은 label/title 등.
  10. setData()는 자식 전체 제거 후 재렌더 — 기존 선택 상태 사라짐.
  11. direction CSS 클래스로 반영 — 실제 flex 방향은 프레임워크 CSS.
  12. 키보드 조작 — 각 라디오는 스페이스로 선택. 화살표 키 그룹 네비는 없음 (표준 라디오 UX 부재).
  13. HTML name 자동 그룹핑 — 같은 그룹 라디오들이 같은 name 속성. 표준 폼 서브밋에서도 배타.
  14. 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으로 대체 가능합니다.


참고

'컴포넌트 > 필드 컴포넌트' 카테고리의 다른 글

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