안녕하세요 이번에 작성하게 될 내용은 제가 react-native에서 저만의 checkbox를 어떤 계기로 구현하게 되었고 어떤 식으로 구현했는지에 대해 전달을 하고자 작성해 봅니다
개발 환경
React-native(expo) + TypeScript로 진행되었습니다.
구현하게 된 계기
요즘 퇴근 후 토이 프로젝트를 진행하고 있습니다.
checkbox를 사용하는 UI가 존재하여 처음에는 checkbox를 사용하기 위해 React-Native 공식 문서에 가서 확인해 보니 이런 문구가 존재했습니다.
제거가 됐다 커뮤니티의 패키지를 사용해라
그래서 저는 제가 사용할 라이브러리를 찾기 시작했고 눈에 들어온 것은 이것이었습니다.
덕분에 편하게 하겠다는 마음에 expo-checkbox를 사용하여 디자인 시스템 와이어 프레임의 checkbox처럼 구현을 해놨습니다.
사용법도 매우 간단하여 마음에 들었습니다.
import Checkbox from 'expo-checkbox';
import React, { useState } from 'react';
import { Text, View } from 'react-native';
export default function App() {
const [isChecked, setChecked] = useState(false);
return (
<View>
<Checkbox
style={styles.checkbox}
value={isChecked}
onValueChange={setChecked}
color={isChecked ? '#4630EB' : undefined}
/>
<Text>checkbox</Text>
</View>
);
}
하지만 제대로 된 디자인 시스템의 checkbox는 제가 예상하던 것과 너무 달랐습니다.
이런 식으로 checkbox가 On 시 disabled 시에 컬러를 변경해야 했습니다.
또한 border-color도 변경해야 했으며 check 시 check 모양의 아이콘도 저희 디자인 시스템에 있는 것을 사용해야 했습니다.
저는 이 작업은 우선순위가 높지 않다 판단하여 시간이 남을 때 새로 다시 구현하기로 하고 TODO를 작성해놨습니다.
TODO 작성을한 지 일주일 정도의 시간이 지났고 이번에 시간이 남아서 구현해 보도록 하겠습니다.
기능 추출
제가 생각하는 checkbox의 기능은 다음과 같습니다.
선택 상태를 표시하는 UI
사용자가 체크박스를 선택하면 선택됐다 표시가 되고 다시 한 번 더 선택하면 표시가 없어집니다
구현
props
interface Props {
isChecked: boolean;
disabled?: boolean;
onValueChangeHandler?: (checked: boolean) => void;
children?: React.ReactNode;
style?: ViewStyle;
}
- isChecked : check의 상태를 받습니다.
- disabled : 비활성화 여부를 받습니다.
- onValueChangeHandler : check 여부를 변경하려고 유저가 check 박스를 눌렀을 때 발생할 callback 함수를 받습니다.
- children : checkbox 옆에 Text(Label)을 사용하는 것이 일반적이기 때문에 그것을 받는 용도로 작성해놨습니다.
- style : checkbox의 margin 또는 padding width 등을 조절할 때 사용하기 위해 넣었습니다.
코드
import React from 'react';
import { StyleSheet, View, Pressable, ViewStyle } from 'react-native';
import Check from './icons/Check';
import Color from '../../constants/color';
interface Props {
isChecked: boolean;
disabled?: boolean;
onValueChangeHandler?: (checked: boolean) => void;
children?: React.ReactNode;
style?: ViewStyle;
}
const UiCheckbox = (props: Props) => {
const { isChecked, disabled, onValueChangeHandler, children, style } = props;
const onPressedHandler = () => {
if (onValueChangeHandler) {
onValueChangeHandler(!isChecked);
}
};
return (
<View style={[styles.container, style]}>
<Pressable
disabled={disabled}
onPress={onPressedHandler}
style={[
styles.checkbox,
isChecked && styles.checked,
disabled && styles.disabled,
isChecked && disabled && styles.checkedAndDisabled,
]}
>
{isChecked && (
<Check size={16} color={disabled ? Color.neutral3 : Color.white} />
)}
</Pressable>
{children && (
<View style={styles.label}>
{children}
</View>
)}
</View>
);
};
export default UiCheckbox;
코드 설명
Pressable을 이용하여 box 형태의 UI를 구성 후 props의 isChecked, disabled 값을 사용하여 스타일링을 추가해 주고 있습니다.
check icon은 props의 isChecked가 true라면 렌더링 시키고 있습니다.
children이 있는 경우 View 태그 안에 자식들을 렌더링 시켜주고 있습니다.
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
},
checkbox: {
height: 24,
width: 24,
borderRadius: 4,
borderWidth: 2,
borderColor: Color.neutral3,
alignItems: 'center',
justifyContent: 'center',
},
checked: {
backgroundColor: Color.primary700,
borderColor: Color.primary700,
},
disabled: {
borderColor: Color.neutral3,
backgroundColor: Color.neutral3,
},
checkedAndDisabled: {
backgroundColor: Color.primary100,
borderColor: Color.primary100,
},
label: {
marginLeft: 8,
},
});
이런 식으로 스타일링을 작성했습니다.
완성된 UI 화면은 이렇습니다. 이로써 저는 디자인 시스템의 checkbox와 동일하게 구현을 했습니다.
개선
UX
현재 코드 상으로 위의 완성된 checkbox의 check라는 Label을 누른다면 check가 되지 않습니다. 그 이유는 Press Event가 존재하지 않기 때문입니다.
하지만 사용자들은 checkbox를 사용할 때 체크박스를 정확히 눌러야 하는 UX보다는 label을 눌러도 check가 활성화가 되는 UX가 더 편하고 익숙합니다.
그에 맞춰서 개선 작업을 진행해 보겠습니다.
방법1
children 이 있을 때 렌더링 하는 태그를 View에서 Preassble 태그로 변경 후 onPress에 triger event를 구현하는 방법입니다.
//추가 함수
const triggerCheckbox = () => {
if (!disabled) {
onPressedHandler();
}
};
//변경 전
{children && (
<View style={styles.label} onPress={triggerCheckbox}>
{children}
</View>
)}
//변경 후
{children && (
<Pressable style={styles.label} onPress={triggerCheckbox}>
{children}
</Pressable>
)}
이런 식으로 label 영역 자체도 press가 가능하도록 변경하고 triggerCheckbox라는 함수를 통해서 마치 체크박스가 눌린 듯이 동작하도록 구현할 수 있습니다.
방법2
가장 최상위의 엘리먼트 요소를 View에서 Pressable로 변경하는 방법도 있습니다.
<Pressable style={[styles.container, style]}
disabled={disabled}
onPress={onPressedHandler}
>
//...child
</Pressable>
주의사항은 onPrees, disabled를 꼭옮겨주셔야 합니다.
설계
제가 개발하고 있는 환경에서는 지금으로서는 checkbox의 size가 24로 고정이 되어 있지 않지만 그렇지 않은 분들 같은 경우 props의 size를 추가해도 좋을 거 같습니다.
props에 추가
size: 'small' | 'medium' | 'large';
size 단위 별로 값 설정
const SIZE = {
small: 16,
medium: 24,
large: 32,
};
이런 식으로 사용할 수 있을 거 같습니다!
style={
{
width: SIZE[size],
height: SIZE[size],
}
}
주로 사용하게 될 사이즈가 medium이라면 props의 size를?를 사용하여 옵셔널 처리로 변경하고
defaultProps를 사용하면 좋을 것 같습니다.
UiCheckbox.defaultProps = {
size: 'medium',
};
후기
이로써 저는 TODO로 남겨놨던 작업을 해결했습니다.
제 생각에 프론트엔드 개발에서 실력을 보여줄 수 있는 부분 중 하나는 공통 컴포넌트를 얼마나 유연하게 구현하느냐가 어느 정도의 포인트는 있다고 생각합니다.
그래서 저는 공통 컴포넌트를 만들 때 이것이 사용될 상황을 상상을 하고는 구현을 합니다 여기서는 이 컴포넌트가 이런 식으로 사용될 것이다 하면서 그런 뒤 사용 용도를 바탕으로 컴포넌트가 해줘야 할 역할을 추리다 보면 제 나름의 생각으로 재사용이 완전히 불가능한 컴포넌트가 나오지는 않는 거 같습니다.
다른 분들의 컴포넌트 설계 구현 팁이 있다면 언제든지 공유해 주시면 감사하겠습니다
'개발' 카테고리의 다른 글
useDebounce를 활용하기 (1) | 2023.06.07 |
---|---|
React Native Chip을 구현해보자 (0) | 2023.05.26 |
내 입맛에 맞게 useList를 구현하기 (0) | 2023.05.10 |
React Native useModal 구현하기 (0) | 2023.05.02 |
useCodePush 구현하기 (3) | 2023.04.24 |