솜이의 데브로그

[React Native] 할 일 관리 애플리케이션(1) 본문

dev/React native

[React Native] 할 일 관리 애플리케이션(1)

somsoming 2021. 11. 5. 00:38

Reference : 처음 배우는 리액트 네이티브 (김범준)

 

 

할 일 관리 애플리케이션

 

  • 등록 : 할 일 항목 추가
  • 수정 : 완료되지 않은 할 일 항목 수정
  • 삭제 : 할 일 항목을 삭제
  • 완료 : 할 일 항목의 완료 상태 관리

 

위의 기능들을 포함한 애플리케이션을 만들어보자.

 

먼저 expo 프로젝트 생성후, 스타일 컴포넌트 라이브러리와 prop-types 라이브러리 설치한다.

 

 

src/theme.js 생성 후 프로젝트에서 사용할 색 정의하기

export const theme={
    background: '#101010',
    itemBackground: '#313131',
    main: '#778bdd',
    text: '#cfcfcf',
    done: '#616161',
};

 

src/App.js

import React from 'react';
import styled, { ThemeProvider } from 'styled-components/native';
import { theme } from './theme';

const Container = styled.View`
    flex: 1;
    background-color: ${({ theme}) => theme.background};
    align-items: center;
    justify-content: center;
`;

export default function App(){
    return(
        <ThemeProvider theme={theme}>
            <Container></Container>
        </ThemeProvider>
    );
};

스타일드 컴포넌트의 ThemeProvider를 이용해 theme 지정하고, 미리 정의한 색을 사용한다.

또 src 내의 App.js가 메인 파일이 되도록 루트 디렉토리의 App.js를 수정한다. (수정 코드는 생략)

 

 

 

타이틀 생성

  • 화면 상단에 TODO List 문구가 렌더링되도록 스타일드 컴포넌트를 이용해 Title 컴포넌트를 만들어보자.

src/App.js

import React from 'react';
import { StatusBar } from 'react-native'; 
import styled, { ThemeProvider } from 'styled-components/native';
import { theme } from './theme';


const Container= styled.SafeAreaView`
    flex: 1;
    background-color: ${({ theme }) => theme.background};
    align-items: center;
    justify-content: flex-start;
`;

const Title = styled.Text`
    font-size: 40px;
    font-weight: 600;
    color: ${({ theme }) => theme.main};
    align-self: flex-start;
    margin: 20px;
`;

export default function App(){
    return(
        <ThemeProvider theme={theme}>
            <Container>
                <StatusBar
                    barStyle="light-content"
                    backgroundColor={theme.background}
                />
                <Title>TODO List</Title>
            </Container>
        </ThemeProvider>
    );
};
  • 앞으로 추가되는 항목들이 위에서부터 정렬되도록 justify-content의 값을 flex-start로 한다.
  • 아이폰처럼 노치 디자인이 있는 기기는 자동으로 padding 값을 적용해 컴포넌트가 가려지지 않도록한다.
 

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

 

 

Input 컴포넌트 생성

 

다음으로, TextInput 컴포넌트를 이용해 Input 컴포넌트를 생성해보자.

먼저 Input 컴포넌트를 만든다.

 

src/components/Input.js

import React from "react";
import styled from "styled-components/native";
import { useWindowDimensions } from 'react-native';

const StyledInput = styled.TextInput`
    width: ${({ width }) => width - 40}px;
    height: 60px;
    margin: 3px 0;
    padding: 15px 20px;
    border-radius: 10px;
    background-color: ${({ theme }) => theme.itemBackground};
    font-size: 25px;
    color: ${({ theme}) => theme.text};
`;

const Input = () => {
    const width = useWindowDimensions().width;

    return <StyledInput width={width} />;
};

export default Input;

 

Dimensions
  • 리액트 네이티브에서는 크기가 다양한 모바일 기기에 대응하기 위해 두가지 방법을 제공한다.
  • 현재 기기 화면의 크기를 알 수 있고, 다양한 크기의 기기에 동일한 모습으로 적용 될 수 있도록 한다.
  • Dimensions 는 처음 받아왔을 때의 크기로 고정하므로 event listener를 등록하여 크기 변화에 대응한다.
  • useWindowDimensions 는 리액트 네이티브에서 제공하는 Hooks 중 하나로, 화면의 크기가 변경되면 자동으로 업데이트한다.

 

Input Component
  • placeholder에 적용할 문자열을 props로 받아 설정하고, 입력 가능한 글자의 수를 제한한다.
  • Input 컴포넌트에서 props로 전달받은 값을 이용한다.
  • TextInput 컴포넌트에서 제공하는 속성을 이용해 키보드 설정을 변경
    • autoCapitalize 속성을 none으로 변경해 자동 대문자 전환을 동작하지 않도록 한다.
    • autoCorrect 속성을 false 로 설정하여 자동 수정 기능을 사용하지 않는다.
    • returnKeyType을 done으로 변경해 키보드의 완료 버튼을 설정한다.
  • TextInput 컴포넌트의 속성들 중에는 특정 플랫폼에만 적용되는 속성이나 값도 있다. (ex: iOS에만 적용되는 keyboardAppearance) → 키보드 색상을 어둡게 설정
이벤트
  • useState를 이용하여 newTask 상태 변수와 세터함수 생성
  • Input 컴포넌트에서 값이 변할 때마다 newTask에 저장한다.
  • 완료 버튼을 누르면 입력된 내용을 확인(alert)하고 Input 컴포넌트를 초기화한다.
  • Input 컴포넌트에서 props로 전달된 값들을 설정하고, 전달되는 값들의 타입과 필수 여부를 지정한다.

 

src/App.js

export default function App(){
    const [newTask, setNewTask] = useState('');

    const _addTask = () => {
        alert(`Add: ${newTask}`);
        setNewTask('');
    };

    const _handleTextChange = text => {
        setNewTask(text);
    };

    return(
        <ThemeProvider theme={theme}>
            <Container>
                <StatusBar
                    barStyle="light-content"
                    backgroundColor={theme.background}
                />
                <Title>TODO List</Title>
                <Input 
                    placeholder="+ Add a Task" 
                    value={newTask}
                    onChangeText={_handleTextChange}
                    onSubmitEditing={_addTask}
                    />
            </Container>
        </ThemeProvider>
    );
}

 

src/components/Input.js

import React from "react";
import styled from "styled-components/native";
import { Dimensions, useWindowDimensions } from 'react-native';
import PropTypes from 'prop-types';

const StyledInput = styled.TextInput.attrs(({ theme }) => ({
    placeholderTextColor: theme.main,
}))`
    width: ${({ width }) => width - 40}px;
    height: 60px;
    margin: 3px 0;
    padding: 15px 20px;
    border-radius: 10px;
    background-color: ${({ theme }) => theme.itemBackground};
    font-size: 25px;
    color: ${({ theme }) => theme.text};
`;

const Input = ({ placeholder, value, onChangeText, onSubmitEditing }) => {
    const width = Dimensions.get('window').width;
    // const width = useWindowDimensions().width;

    return (
        <StyledInput 
            width={width} 
            placeholder={placeholder} 
            maxLength={50}
            autoCapitalize="none"
            autoCorrect={false}
            returnKeyType="done" 
            keyboardAppearance="dark"
            value={value}
            onChangeText={onChangeText}
            onSubmitEditing={onSubmitEditing}
        />
    );
};

Input.propTypes = {
    placeholder: PropTypes.string,
    value: PropTypes.string.isRequired,
    onChangeText: PropTypes.func.isRequired,
    onSubmitEditing: PropTypes.func.isRequired,
};

export default Input;

 

 

 

할 일 목록 만들기

 

Input 컴포넌트를 통해 입력받은 내용을 목록으로 출력해보자

  • IconButton 컴포넌트 : 완료, 수정, 삭제 버튼
  • Task 컴포넌트 : 목록의 각 항목

https://fonts.google.com/icons

Google Material Design 에서 아이콘 이미지를 다운받아 준비한다.

 

 

IconButton 컴포넌트

아이콘 이미지를 관리할 images.js 폴더 생성

 

src/images.js

import CheckBoxOutline from '../assets/icons/check_box_outline.png';
import CheckBox from '../assets/icons/check_box.png';
import DeleteForever from '../assets/icons/delete.png';
import Edit from '../assets/icons/edit.png';

export const images = {
    uncompleted: CheckBoxOutline,
    completed: CheckBox,
    delete: DeleteForever,
    update: Edit,
};

 

src/components/IconButton.js

import React from "react";
import { TouchableOpacity } from "react-native";
import styled from "styled-components/native";
import PropTypes from 'prop-types';
import { images } from '../images';

const Icon = styled.Image`
    tint-color: ${({ theme }) => theme.text};
    width: 30px;
    height: 30px;
    margin: 10px;
`;

const IconButton = ({ type, onPressOut }) => {
    return(
        <TouchableOpacity onPressOut={onPressOut}>
            <Icon source={type} />
        </TouchableOpacity>
    );
};

IconButton.propTypes = {
    type: PropTypes.oneOf(Object.values(images)).isRequired,
    onPressOut: PropTypes.func,
};


export default IconButton;
  • IconButton 컴포넌트 호출 시 원하는 이미지의 종류를 props에 type으로 전달
  • 아이콘의 색은 입력되는 텍스트와 동일한 색 적용

 

Task 컴포넌트

완료 여부 확인 버튼, 입력된 할 일 내용, 항목 삭제, 수정 버튼으로 구성

 

src/components/Task.js

import React from "react";
import styled from "styled-components/native";
import PropTypes from 'prop-types';
import IconButton from "./IconButton";
import { images } from '../images';

const Container = styled.View`
    flex-direction: row;
    align-items: center;
    background-color: ${({ theme }) => theme.itemBackground};
    border-radius: 10px;
    padding: 5px;
    margin: 3px 0px;
`;

const Contents = styled.Text`
    flex: 1;
    font-size: 24px;
    color: ${({ theme }) => theme.text};
`;

const Task = ({ text }) => {
    return(
        <Container>
            <IconButton type={images.uncompleted} />
            <Contents>{text}</Contents>
            <IconButton type={images.update} />
            <IconButton type={images.delete} />
        </Container>
    );
};

Task.propTypes = {
    text: PropTypes.string.isRequired,
};


export default Task;
  • 할 일 내용은 props로 전달받은 값을 입력하며, 각 아이콘을 입력한다.
  • App.js에 Task 컴포넌트 목록을 만든다
    • 리액트 네이티브에서 제공하는 ScrollView 컴포넌트 이용. (화면을 넘어가도 스크롤 이용)
    • 양쪽의 공백을 동일하게 하기 위해 Dimensions로 화면 너비 구한 후 스타일에 사용

 

결과

'dev > React native' 카테고리의 다른 글

[React Native] Hooks  (0) 2021.11.12
[React Native] 할 일 관리 애플리케이션(2)  (0) 2021.11.06
[React Native] 스타일링  (0) 2021.10.16
[React Native] 컴포넌트  (0) 2021.10.10
[React Native] JSX  (0) 2021.10.10