똑같은 삽질은 2번 하지 말자
헷갈리는 Checkbox와 Radio의 컴포넌트 본문
개요
React, Vue를 활용해 개발을 하는 중인데 Vue일때는 이런식으로 개발을 하고 React일때는 이런식으로 개발을 하는등등 방식이 다르고 근본적이 처리에 대해 제대로 인식이 안되어 있는 듯해 이 글을 작성하면서 정리하고자 한다.
용도
- Radio: 일반적으로 사용자가 주어진 옵션 중 하나만 선택할 수 있을 때 사용된다. 같은 이름(name 속성)을 공유하는 라디오 버튼 그룹에서는 오직 하나의 옵션만이 선택될 수 있어야 한다.
- Checkbox: 사용자가 여러 옵션을 동시에 선택할 수 있게 해준다. 각 체크박스는 독립적으로 동작하며, 여러 옵션을 동시에 선택/선택 해제할 수 있어야 한다.
Vue로 구현하기
Radio
<input type="radio" v-model="picked" value="One">
<input type="radio" v-model="picked" value="Two">
일반적으로 v-model을 사용하여 단일 데이터 속성에 바인딩한다. 라디오 버튼 그룹 내의 모든 버튼이 같은 v-model을 공유하며, 선택된 라디오 버튼의 value가 picked에 할당된다.
Checkbox
<input type="checkbox" v-model="checkedNames" value="Jack">
<input type="checkbox" v-model="checkedNames" value="John">
개별 체크박스는 각각의 데이터 속성에 바인딩할 수 있으며, 또한 v-model을 배열에 바인딩하여 여러 체크박스를 그룹화할 수도 있다. 체크박스의 value는 체크되었을 때 v-model에 바인딩된 배열에 추가되고, 체크 해제되면 배열에서 제거된다.
Vue의 경우에는 v-model을 활용하면 상당히 심플하게 Radio와 Checkbox를 구현할 수 있다.
React로 구현하기
Radio
function RadioExample() {
const [selectedOption, setSelectedOption] = useState('option1');
const handleChange = (event) => {
setSelectedOption(event.target.value);
};
return (
<div>
<label>
<input
type="radio"
checked={selectedOption === 'option1'}
onChange={handleChange}
/>
Option 1
</label>
<label>
<input
type="radio"
checked={selectedOption === 'option2'}
onChange={handleChange}
/>
Option 2
</label>
</div>
);
}
Checkbox
function CheckboxExample() {
const [checkedItems, setCheckedItems] = useState(new Map());
const handleChange = (event) => {
const item = event.target.name;
const isChecked = event.target.checked;
setCheckedItems(prev => new Map(prev).set(item, isChecked));
};
return (
<div>
<label>
<input
type="checkbox"
checked={checkedItems.get('item1') || false}
onChange={handleChange}
/>
Item 1
</label>
<label>
<input
type="checkbox"
checked={checkedItems.get('item2') || false}
onChange={handleChange}
/>
Item 2
</label>
</div>
);
}
리액트의 경우에는 v-model이 단독으로 해주던걸 각각 checked와 onChange로 나누어서 조금 더 구체적으로 처리를 해야 한다. (물론 위와 다른 방식으로도 구현할 수 있다. )
컴포넌트로 구현하기(Vue, React 공통)
개별 컴포넌트로 만들려고 할 때 props를 어떻게 구성하지? 에 대해 항상 고민하는데 이는 요구사항에 따라 달라질 수 있겠지만 라디오나 체크박스 컴포넌틑는 오로지 UI적인 요소만 가지는 Dumb한(멍청한)? 컴포넌트로 만드는게 좋다. 왜냐하면 Dumb Component로 만들어서 UI적인 요소만 잘라내면 거의 모든패턴에 재활용이 가능하기 때문이다.
그럼 CheckBox의 Props를 생각해보자
type CheckBoxItemBaseType = {
text: string;
checked: boolean;
disabled?: boolean;
indeterminate?: boolean;
change: (checked: booelan) => void;
};
- text: 체크박스의 라벨
- checked: 현재 체크가 되어 있는지의 여부
- diabled: 비활성화 여부
- indeterminate: 중간체크 여부 (보통 전체체크 체크박스가 별도로 존재하며 그 하위의 체크박스가 일부만 체크되어 있을때 true가 된다.)
- change: 핸들링 메소드
여기서 value를 props로 받지 않고있는 이유는 보통 value는 부모 컴포넌트에서만 알고 있으면 되고 체크박스 컴포넌트는 checked값만 알고 있으면 문제가 없다.
라디오의 경우는 이하의 props를 가지도록 구성한다.
type RadioProps {
readonly checkedKey: string | number | boolean;
readonly disabled?: boolean;
readonly radioItems: {radioKey: string | number | boolean, text: string};
}
- checkedKey: 현재 체크되어 있는 값, radioItems안에 있는 radioKey값과 비교해서 현재 체크된 라디오를 구별할 수 있다.
- disabled: 비활성화 여부
- radioItems: 라디오로 들어갈 친구들의 정보
라디오의 경우에는 절대적으로 복수의 라디오 버튼이어야 UI로써의 기능을 하기 때문에 애초에 radioItems로 복수의 라디오 버튼을 표시하게끔 한다.