티스토리 뷰
react-native를 설치한데 이어 간단한 Todo앱을 구현해보았다.
- input에 할일을 적고 add 버튼을 누르면 하단 리스트에 추가된다.
- checkbox를 클릭하면 todo에서 할일을 했다는 표시로 글자 가운데 줄이 그어진다.
- 각 row는 delete 버튼이 있어서 리스트에서 제거할 수 있다.
레이아웃
react-native는 기본적으로 화면을 구성하기 위해 내장 컴포넌트인 View 컴포넌트를 사용하며, StyleSheet를 이용하여 스타일을 구성한다.
StyleSheet는 css의 스타일 기능을 제공하며, camel case 방식을 사용한다.
Container
앱 화면을 구성하는 container
react-native는 ios, android 모두 지원하므로 둘 다의 레이아웃을 적용해야 한다.
ios는 상단 노치에 의해 레이아웃이 가려지는 부분 때문에 추가적으로 설정을 해주어야 한다.
SafeAreaView를 이용하여 수동으로 레이아웃을 잡아주어야 한다.
https://reactnative.dev/docs/safeareaview
SafeAreaView · React Native
The purpose of SafeAreaView is to render content within the safe area boundaries of a device. It is currently only applicable to iOS devices with iOS version 11 or later.
reactnative.dev
import React from 'react';
import {
SafeAreaView,
View,
StyleSheet,
Platform,
type ViewStyle,
} from 'react-native';
import Contents from 'expo-constants';
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
},
});
type Props = {
children: React.ReactNode;
style?: ViewStyle;
};
function ViewTemplate({ children, style }: Props) {
const padding = Platform.OS === 'ios' ? 16 : Contents.statusBarHeight + 16;
return (
<SafeAreaView>
<View
style={{
...styles.container,
paddingTop: padding,
...style,
}}
>
{children}
</View>
</SafeAreaView>
);
}
export default ViewTemplate;
react-native에서 Platform을 통해 ios인지, android인지 확인할 수 있다.
이를통해 OS별 분기처리가 가능하다.
전체 레이아웃은 위 컴포넌트로 구성하였다.
Input
react-native의 TextInput컴포넌트를 이용하여 input을 구성한다.
사용법은 html의 input태그와 사용하는것이 크지는 않지만, onChange이벤트는 onChangeText라는 props로 넘겨준다.
Button
react-native에서 제공하는 Button컴포넌트가 있지만 스타일 적용이 까다로운것 같아서 다른 컴포넌트를 사용하였다.
TouchableOpacity컴포넌트를 이용하여 스타일링이 용이한 컴포넌트를 만들었다.
그리고 클릭이벤트는 onPress를 이용하여 구현한다.
Checkbox
과거에 react-native에서 Checkbox가 있었지만 현재는 사라진것으로 보인다.
따라서 서드파티 모듈에서 Checkbox를 찾아야 헀는데 expo-checkbox라는 라이브러리를 통해 Checkbox를 구현할 수 있었다.
import React, { useRef, useState } from 'react';
import Typo from '@/components/Typo';
import ViewTemplate from '@/components/ViewTemplate';
import {
StyleSheet,
View,
TextInput,
Text,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Checkbox from 'expo-checkbox';
const styles = StyleSheet.create({
container: {
paddingLeft: 12,
paddingRight: 12,
},
title: {
fontSize: 36,
fontWeight: 'bold',
},
todoContainer: {
rowGap: 16,
width: '100%',
},
addArea: {
flexDirection: 'row',
columnGap: 16,
height: 40,
},
input: {
width: Dimensions.get('window').width - 140,
borderRadius: 4,
borderWidth: 1,
height: 50,
fontSize: 20,
paddingHorizontal: 4,
},
addBtnBackground: {
backgroundColor: '#d0d0d0',
paddingVertical: 15,
paddingHorizontal: 30,
borderRadius: 4,
marginBottom: 20,
height: 50,
justifyContent: 'center',
alignItems: 'center',
},
addBtnText: {
color: '#000',
fontSize: 12,
},
todoListArea: {
rowGap: 8,
},
row: {
flexDirection: 'row',
},
left: {
flexDirection: 'row',
alignItems: 'center',
columnGap: 4,
},
todoText: {
width: Dimensions.get('window').width - 148,
},
deleteBtnBg: {
backgroundColor: '#d0d0d0',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 4,
},
deleteBtnText: {
color: '#000',
fontSize: 12,
},
});
export default function HomeScreen() {
const inputRef = useRef<TextInput | null>(null);
const [value, setValue] = useState('');
const [list, setList] = useState<
{ key: number; value: string; checked: boolean }[]
>([]);
const onChangeText = (v: string) => {
setValue(v);
};
const onAdd = () => {
if (value === '') return;
setList([
...list,
{
key: new Date().getTime(),
value,
checked: false,
},
]);
setValue('');
inputRef.current?.blur();
};
const onDelete = (idx: number) => {
setList((prev) => prev.filter((_, i) => idx !== i));
};
const onChangeCheck = (idx: number, checked: boolean) => {
setList((prev) =>
prev.map((state, i) => ({
...state,
checked: i === idx ? checked : state.checked,
})),
);
};
return (
<ViewTemplate
style={{
padding: 0,
rowGap: 14,
backgroundColor: '#fff',
height: '100%',
}}
>
<Typo typo='t'>Todo List</Typo>
<View style={styles.todoContainer}>
<View style={styles.addArea}>
<TextInput
style={styles.input}
onChangeText={onChangeText}
value={value}
ref={inputRef}
></TextInput>
<TouchableOpacity onPress={onAdd} style={styles.addBtnBackground}>
<Text style={styles.addBtnText}>Add</Text>
</TouchableOpacity>
</View>
<View style={styles.todoListArea}>
{list.map(({ value, checked, key }, idx) => {
return (
<View style={styles.row} key={`${idx}-${key}`}>
<View style={styles.left}>
<Checkbox
value={checked}
onValueChange={() => {
onChangeCheck(idx, !checked);
}}
></Checkbox>
<Text
style={{
...styles.todoText,
textDecorationLine: checked ? 'line-through' : 'none',
}}
>
{value}
</Text>
</View>
<TouchableOpacity
style={styles.deleteBtnBg}
onPress={() => {
onDelete(idx);
}}
>
<Text style={styles.deleteBtnText}>Delete</Text>
</TouchableOpacity>
</View>
);
})}
</View>
</View>
</ViewTemplate>
);
}
왼쪽 안드로이드, 오른쪽 IOS

'front-end > react' 카테고리의 다른 글
react-native 설치해보기 (0) | 2024.06.08 |
---|---|
React query (0) | 2023.05.03 |
Redux 구현 (0) | 2023.05.03 |
가상 돔, Virtual DOM (0) | 2023.05.03 |
React useState, useEffect Mechanism(with. closure) (0) | 2023.05.03 |