처음 배우는 리액트 네이티브
5장 할 일 관리 애플리케이션
youbing
2025. 1. 22. 22:13
본 실습 내용은 아래 깃허브에 자세히 작성하였습니다.
https://github.com/studyReactNative/myTodo
GitHub - studyReactNative/myTodo
Contribute to studyReactNative/myTodo development by creating an account on GitHub.
github.com
목표 기능
- 등록 : 할 일 항목을 추가
- 수정 : 완료되지 않은 할 일 항목을 수정
- 삭제 : 할 일 항목을 삭제
- 완료 : 할 일 항목의 완료 상태를 관리
5.1 프로젝트 준비하기
npx @react-native-community/cli init todo
cd todo
npm install styled-components prop-types
- styled-components를 설치하는 과정에서 react / react-dom 버전 불일치 문제가 생겨 아래의 작업을 추가로 실행함. (추가 : React-Native styled-components 설치 오류 해결 방법)
5.2 타이틀 만들기
SafeAreaView 컴포넌트
- iOS에서 일부 기종에 있는 노치 디자인이 Title 컴포넌트의 일부를 가림.
- SafeAreaView는 이를 해결하기 위해 자동으로 padding을 적용시키는 컴포넌트.
-> src/App.js에 있는 Container 컴포넌트를 View가 아닌 SafeAreaView 컴포넌트로 수정함.
const Container = styled.SafeAreaView`
flex: 1;
align-items: center;
justify-content: flex-start;
background-color: ${({theme}) => theme.background};
`;
StatusBar 컴포넌트
- 안드로이드에서 상태 바(status bar)가 Title 컴포넌트의 일부를 가림.
- 또한, 배경색을 어두운 색으로 설정하면서 상태 바의 내용도 눈에 잘 들어오지 않음.
- StatusBar는 상태 바를 제어하여 스타일을 변경하는 컴포넌트.
-> StatusBar 컴포넌트를 추가하여 상태 바 배경색을 통일하고 아이콘을 밝게 표시함.
<StatusBar barStyle="light-content" backgroundColor={theme.background} />
- barStyle : 상태 표시줄의 텍스트와 아이콘 색상 조절
- default: 플랫폼에 따라 다름. 안드로이드에서는 텍스트가 밝게, iOS에서는 어둡게 표시됨.
- light-content: 텍스트와 아이콘을 밝게 표시.
- dark-content: 텍스트와 아이콘을 어둡게 표시.
- backgroundColor : 상태 표시줄의 배경색 조절 (Only 안드로이드)
- iOS에서는 직접 조절할 수 없으며, 대신 SafeAreaView나 배경 뷰의 색상으로 간접 조정.
5.3 Input 컴포넌트 만들기
- 할 일 항목 추가 + 등록된 할 일 항목을 수정할 때 사용
Dimensions
- Input 컴포넌트가 화면에 꽉 차서 답답해 보임.
- 리액트 네이티브에서는 크기가 다양한 모바일 기기에 대응하기 위해 현재 화면의 크기를 알 수 있는 Dimensions와 useWindowDimensions를 제공함.
- Dimensions는 처음 값을 받아왔을 때의 크기로 고정 -> 이벤트 리스너를 등록하여 화면 회전 등의 크기 변화에 대응할 수 있게 함.
- useWindowDimensions는 리액트 네이티브에서 제공하는 Hooks 중 하나로, 화면의 크기가 변경되면 화면의 크기, 너비, 높이를 자동으로 업데이트 함.
import React from 'react';
import {Dimensions, useWindowDimensions} from 'react-native';
import styled from 'styled-components';
const Input = () => {
const width = useWindowDimensions().width;
// const width = Dimensions.get('window').width;
return <StyledInput width={width} />;
};
export default Input;
const StyledInput = styled.TextInput`
width: ${({width}) => width - 40}px;
...
`;
추가 : placeholder 스타일에 따른 React와 React Native의 차이
플랫폼 | 방법 | 설명 |
React | ::placeholder | CSS 가상 선택자로 placeholder 스타일 설정 가능 |
React Native | placeholderTextColor prop | 별도의 prop으로 placeholder 색상 설정 필요 |
- React Native는 DOM 기반이 아니라 네이티브 UI 컴포넌트를 사용하므로, 웹에서 사용하는 CSS 가상 선택자(::placeholder, :hover 등)를 지원하지 않음.
- TextInput 컴포넌트는 default로 첫 글자가 대문자로 나타나고(autoCapitalize 속성), 오타 입력 시 자동으로 수정하는 기능이 켜져 있음(autoCorrect 속성).
iOS의 경우 키보드의 완료 버튼이 return으로 되어 있음(returnKeyType 속성).
TextInput 컴포넌트 이벤트
- onChangeText : 입력값이 변경될 때마다 호출되는 콜백 함수(≒ onChange)
- 입력된 텍스트 값이 콜백 함수의 1번째 매개변수가 됨.
- onSubmitEditing : 사용자가 키보드에서 '완료" 또는 "Enter"를 눌렀을 때 호출되는 콜백 함수 (≒ onSubmit)
추가 : alert import 오류 수정
- alert 메소드를 사용하려면 Alert 모듈을 import 해야 함.
import {Alert} from 'react-native';
const addTask = () => {
Alert.alert(`~~`);
...
};
5.4 할 일 목록 만들기
만들 컴포넌트
- IconButton 컴포넌트 : 완료, 수정, 삭제 버튼으로 사용할 컴포넌트
- Task 컴포넌트 : 목록의 각 항목으로 사용할 컴포넌트
추가 : Image 컴포넌트 tint-color 오류 수정
- tintColor는 React Native의 특정 컴포넌트(Image)에서만 사용할 수 있는 속성으로, 범용 스타일 속성이 아니기 때문에 styled-components에서 CSS 속성처럼 사용할 수 없음.
- width나 height 등은 모든 컴포넌트에서 작동하는 범용 속성으로, React Native의 스타일 시스템에서 지원되기 때문에 문제없이 작동함.
-> tintColor 속성을 attrs로 따로 작성해야 한다.
// 수정 전
const Icon = styled.Image`
tint-color: ${({theme}) => theme.text}
...
`;
// 수정 후
const Icon = styled.Image.attrs(({theme}) => ({
tintColor: theme.text,
}))`
...
`;
다른 방법 : StyleSheet + useContext
- StyleSheet는 정적 스타일을 정의하는 데 사용되어, props를 직접 받을 수 없음. -> tintColor를 인라인 스타일로 정의
- styled-components는 ThemeProvider와 함께 사용할 때, 자동으로 useContext를 통해 theme 값을 읽어옴. -> 수동으로 불러와야 함.
import React, {useContext} from 'react';
import {Image, Pressable, StyleSheet} from 'react-native';
import {ThemeContext} from 'styled-components';
...
const IconButton = ({type, onPressOut}) => {
const theme = useContext(ThemeContext); // ContextAPI로 정의한 theme 불러오기
return (
<Pressable hitSlop={10} onPressOut={onPressOut}>
<Image source={type} style={[styles.icon, {tintColor: theme.text}]} />
</Pressable>
);
};
...
const styles = StyleSheet.create({
icon: {
width: 30,
height: 30,
margin: 10,
},
});
ScrollView 컴포넌트
- 스크롤 가능한 영역을 생성할 수 있게 해줌.
- default : 세로 스크롤, 스크롤바 제공
- 주의 : 전체 자식 요소를 한 번에 렌더링하기 때문에, 자식 요소가 매우 많으면 성능 저하 발생 (대신, FlatList, SectionList 사용 가능)
- 속성
- horizontal={true} : 가로 스크롤 활성화
- showVerticalScrollIndicator : 세로 스크롤바 표시 여부 설정 (Horizontal도 가능)
- contentContainerStyle : ScrollView 내부 컨텐츠 스타일 지정
5.5 기능 구현하기
할 일 추가 기능 부분 중
- Object.values(tasks).map(~) : tasks가 객체로 정의되어 있어 배열처럼 바로 map 메소드를 호출할 수 없음. 그래서 객체의 값만 불러오는 Object.values(객체)를 먼저 호출해서 배열로 바꾸고 map 메소드 사용.
- [ID] : { id: ~, text: ~, completed: ~} : [ID]로 설정해야 동적으로 객체의 키를 설정할 수 있음. ID로만 하게 되면 "ID"라는 고정된 키로 설정되어 덮어쓰기 문제가 발생할 수 있음.
할 일 삭제 기능 부분 중
- Object.assign({}, tasks) : 객체 (얕은) 복사를 위한 메소드.
- 복사해서 삭제하는 이유 : tasks는 useState로 정의된 변수라 setTasks로만 변경 가능.
추가 : 얕은 복사 vs 깊은 복사
예시 - A를 복사해서 새로운 B를 만들었다고 가정.
공통점 : A, B는 서로 다른 객체
차이점 : 객체 내부의 객체나 배열을 변경할 때
- 얕은 복사 : 내부의 객체나 배열이 참조로 복사되기 때문에, 그 부분을 변경하면 A, B 모두 영향을 미침.
( const newTasks = Object.assign({}, tasks); )
- 깊은 복사 : A, B의 변경사항들이 서로 영향을 끼치지 않고 독립적으로 존재함.
( const newTasks = JSON.parse(JSON.stringify(tasks)); )
- defaultProps : 곧 사용되지 않을 예정이라는 경고가 뜸. -> JS 기본 기능으로 default 값을 지정
// 기존 코드
IconButton.defaultProps = {
onPressOut: () => {},
};
// 수정한 코드
const IconButton = ({ type, onPressOut = () => {}, id }) => { ... }
입력 취소 기능 부분 중
- onBlur : 포커스를 잃을 때 호출되는 이벤트(↔ onFocus)
- 주로 입력값 확인, 폼 유효성 검사, 스타일 변경 등에 사용
5.6 부가 기능
데이터 저장하기 & 데이터 불러오기
- AsyncStorage : 비동기로 동작하며 문자열로 된 키-값 형태의 데이터를 기기(로컬)에 저장하고 불러오는 기능 제공
npm install @react-native-async-storage/async-storage
- setItem(키, 값) : 데이터(문자열 형식) 저장
- getItem(키) : 지정된 key에 저장된 데이터 읽기 (데이터가 없을 경우 null 반환)
- removeItem(키) : 지정된 key에 저장된 데이터 삭제 (데이터 없어도 에러 X)
- clear() : 저장된 모든 데이터 삭제
- getAllKeys() : AsyncStorage에 저장된 모든 키 가져오기
- 데이터를 저장할 때 JSON 형식으로 문자열화(JSON.stringify)한 경우, getItem으로 가져온 데이터를 JSON.parse로 다시 변환해야 함.
로딩 화면과 아이콘
- AppLoading 컴포넌트 : 특정 조건에서 로딩 화면이 유지되도록 하는 기능 (Expo에서만 지원)
- 렌더링하기 전에 처리해야 하는 작업을 수행하는 데 유용하게 사용됨.
- startAsync : Apploading 컴포넌트가 동작하는 동안 실행될 함수
- onFinish : startAsync가 완료되면 실행할 함수
- onError : startAsync에서 오류가 발생하면 실행할 함수
- AppLoading 컴포넌트를 대신해서 React Native CLI에서는 로딩 화면을 직접 구현해야 함. -> ActivityIndicator
- ActivityIndicator 컴포넌트 : React Native에서 제공하는 기본 로딩 인디케이터 컴포넌트
- 데이터 로드 중이거나 비동기 작업이 진행 중임을 사용자에게 알리기 위해 회전하는 원형 애니메이션 제공
- 플랫폼 독립적이며, 플랫폼에 따라 기본 스타일이 약간씩 다르게 표시됨. (iOS - UIActivityIndicatorView / 안드로이드 - ProgressBar)
export default function App() {
const [isLoading, setIsLoading] = useState(false);
...
useEffect(()=>{
try {
setIsLoading(true);
loadTasks();
} catch (e) {
console.log(e);
} finally {
setTimeout(()=>{
setIsLoading(false);
}, 500);
}
});
...
return (
<ThemeProvider ... >
{isLoading ? (
<ActivityIndicator size="large" color="#000" style={{flex: 1}} />
) : (
<Container> ... </Container>
)}
</ThemeProvider>
);
}
- size : 인디케이터 크기 설정 (small, large, 픽셀)
- color : 인디케이터 색상 지정
- animating : 로딩 애니메이션의 표시 여부 제어 (default = true, 애니메이션 표시)
본문에서 설명하는 내용과 사용한 이미지는 책 '처음 배우는 리액트 네이티브'를 참고하였습니다.