처음 배우는 리액트 네이티브

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

 


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나 배경 뷰의 색상으로 간접 조정.

 

before -> after


5.3 Input 컴포넌트 만들기

  • 할 일 항목 추가 + 등록된 할 일 항목을 수정할 때 사용

 

Dimensions

  • Input 컴포넌트가 화면에 꽉 차서 답답해 보임.
  • 리액트 네이티브에서는 크기가 다양한 모바일 기기에 대응하기 위해 현재 화면의 크기를 알 수 있는 DimensionsuseWindowDimensions를 제공함.
  • 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 속성처럼 사용할 수 없음.
  • widthheight 등은 모든 컴포넌트에서 작동하는 범용 속성으로, 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) : 객체 (얕은) 복사를 위한 메소드.
    • 복사해서 삭제하는 이유 : tasksuseState로 정의된 변수라 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, 애니메이션 표시)

 

 

 

본문에서 설명하는 내용과 사용한 이미지는 책 '처음 배우는 리액트 네이티브'를 참고하였습니다.