[React Native] To-do list 만들기(1)

­Elena·2021년 11월 12일
1
post-thumbnail

개요

  • 등록(add) : to-do list 추가
  • 수정(edit) : to-do list 수정
  • 삭제(delete) : to-do list 삭제
  • 완료(complete) : to-do list 완료 상태

예상 UI

todo list 만들기(1)에서는 기본 UI 구성만 할 것이고, todo list 만들기(2)에서는 기능 구현을 할 것이다.


프로젝트 준비하기

프로젝트 생성

expo init [project name]

프로젝트에서 사용할 라이브러리 설치 : styled-components, prop-types

cd [project name]
npm i styled-components prop-types

src 폴더 생성 -> App.js 파일 생성

src 외부에 있는 App.js

import App from './src/App';
export default App;

src/App.js

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
  
export default function App() {
  return (
    <View style={styles.container}>
      <Text>start</Text>
      <StatusBar style="auto" />
    </View>
  );
}
  
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

src 폴더 아래에 component 폴더 생성


1. 타이틀 컴포넌트 만들기

Title component 만들기

src/components/Title.js

import React from 'react';
import { StyleSheet, Text } from 'react-native';

const Title = ({ title }) => {
  return <Text style={styles.title}>{title}</Text>;
};

const styles = StyleSheet.create({
  title: {
    fontSize: 40,
    fontWeight: '600',
    color: 'black',
    margin: 30,
  },
});

export default Title;
  • title을 props로 받아온다.

src/App.js

import Title from './components/Title';

export default function App() {
  return (
    <View style={styles.container}>    
      <Title title="todo list"></Title>
      <Text>start</Text>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'flex-start',
    justifyContent: 'flex-start',
  },
});
  • justifyContent, alignItems : 'flex-start'로 변경

문제점

상단바와 메인 화면(컴포넌트)이 겹치는 문제 발생

해결책

  1. SafeAreaView 컴포넌트 사용 (iOS)

    자동으로 padding 값이 적용되어 노치 디자인 문제를 해결한 수 있는 컴포넌트이다.

  2. StatusBar 컴포넌트 사용 (Andriod)

    리액트 네이티브에서는 상태 바를 제어할 수 있는 StatusBar 컴포넌트를 제공한다. 이 컴포넌트를 사용하면 상태 바의 스타일을 변경할 수 있고, 서로 겹치는 문제를 해결할 수 있다.


2. Input 컴포넌트 만들기

https://reactnative.dev/docs/textinput

TextInput 컴포넌트 기본

src/components/Input.js

import React from 'react';
import { Dimensions, StyleSheet, Text, TextInput } from 'react-native';

const Input = () => {
  return (
    <TextInput style={styles.input} placeholder="+ Add a task" maxLength={50} />
  );
};

const styles = StyleSheet.create({
  input: {
    width: Dimensions.get('window').width - 15,
    fontSize: 20,
    backgroundColor: '#f1f3f5',
    borderWidth: 1,
    borderRadius: 5,
    margin: 5,
    padding: 8,
    alignItems: 'center',
  },
});

export default Input;
  • placeholder : props로 받아올 수도 있다.
  • maxLength : 입력 가능한 최대 글자수 제한
  • autoCapitalize : 자동으로 첫글자 대문자
  • autoCorrect : 자동으로 오타 수정
  • returnKeyType : 완료버튼 default - return, "done"으로 변경가능(적용 가능한 값이 아이폰과 안드로이드가 다르다.)
  • keyboardAppearance : 아이폰의 키보드 색상 변경

Dimensions 사용해보기

RN에서는 크기가 다양한 모바일 기기에 대응하기 위해 현재 화면의 크기를 알 수 있는 2가지를 제공한다.

  1. Dimensions
  2. useWindowDimensions

위의 두 기능 모두 현재 기기의 화면 크기를 알 수 있고, 이를 이용해 다양한 크기의 키키에 동일한 모습으로 적용될 수 있도록 코드를 작성할 수 있다.

  1. Dimensions

처음 값을 받아왔을 때의 크기로 고정되지 때문에 기기를 회전해서 화면이 전환되면 변환된 화면의 크기와 일치하지 않을 수 있다. 이런 상황을 위해 이벤트 리스너를 등록하여 화면의 크기 변화에 대응할 수 있도록 기능을 제공한다.

const styles = StyleSheet.create({
  input: {
    width: Dimensions.get('window').width - 15,
    //생략
  },
});
  1. useWindowDimensions

RN에서 제공하는 Hooks중 하나로, 화면의 크기가 변경되면 화면의 크기, 너비, 높이를 자동으로 업데이트 한다.

width : useWindowDImensions().width - 15;

이벤트

useState 사용

useState를 사용하여 newTask 상태 변수와 setNewTask 세터 함수를 생성.
_handleTextChange() : Input 컴포넌트에서 값이 변할 때마다 newTask에 저장하도록 작성했다.
_addTask() : 완료버튼을 누르면 입력된 내용을 확인하고 Input 컴포넌트를 초기화하도록 만들었다.

  • onSubmitEditing : input이 submit 되었을 때 불리는 함수
  • onChangeText : input의 text가 변경되었을 때 불리는 함수
    추가로 onSubmitEditing과 같은 기능을 하는 Button도 만들어 보았다.

app.js

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

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

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

  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Title title="Todo List✔️"></Title>
      <Input
        value={newTask}
        onChangeText={_handleTextChange}
        onSubmitEditing={_addTask}
      />
      <Button
        title="enter"
        onPress={() => {
          _addTask();
        }}
      />
    </View>
  );
}

components/Input.js

const Input = ({ value, onChangeText, onSubmitEditing }) => {
  return (
    <TextInput
      style={styles.input}
      placeholder="+ Add a task"
      maxLength={50}
      value={value}
      onChangeText={onChangeText}
      onSubmitEditing={onSubmitEditing}
    />
  );
};

삽질 : 처음에 props 받아오는걸 {} 이 괄호를 안쳐서 값이 제대로 안나왔다. 이상하다 하면서 구글링만 한참하다가 나중에 깨달음...🥲


3. 할 일 목록 만들기(Task 컴포넌트)

컴포넌트 목록

  • task 컴포넌트 : 할 일 목록
  • icon button 컴포넌트 : 완료, 수정, 삭제 버튼

아이콘 준비하기

  1. 아이콘 자체를 사용하는 방법

vector-icons는 Expo 프로젝트에서 기본적으로 설치되는 라이브러리이다.
여기에서 아이콘을 확인할 수 있다.

오른쪽 Filters를 눌러보면 다양한 아이콘 종류가 있는 것을 확인 할 수 있다. 나는 이 프로젝트에서 MaterialCommunityIcons만을 사용했지만 선호도에 따라 자유롭게 선택할 수 있다. 대신 import를 꼭 해주어야 한다.

  1. 아이콘 이미지를 사용하는 방법

여기에서 아이콘 이미지를 다운로드 받는다.
아이콘을 다운 받으면 이미지와 함께 2배, 3배 사이즈의 아이콘이 함께 있는 것을 볼 수 있다. 다운로드가 완료된 3개의 이미지를 각각 '파일명.png', '파일명@2x.png'로 변경한다. 이렇게 파일명을 동일한 이름으로 사용하면서 뒤에 @2x, @3x를 붙이면 RN에서 화면 사이즈에 알맞은 크기의 이미지를 자동으로 불러와 사용한다.
아이콘 이미지는 assets 폴더 아래 icons 폴더를 생성하여 넣어둔다.

Icon Button 컴포넌트 만들기

react native docs
RN에서 image component를 사용하는 방법은 위 링크에서 확인할 수 있다. 프로젝트에 있는 이미지 파일의 경로나 URL을 이용해서 원격에 있는 이미지를 렌더링할 수 있다.

먼저 이미지를 관리할 images.js 파일을 생성한다.
images.js

import CheckBoxOutline from '../assets/icons/outline_check_box_outline_blank_black_24dp';
import CheckBox from '../assets/icons/outline_check_box_black_24dp';
import Delete from '../assets/icons/outline_delete_black_24dp';
import Edit from '../assets/icons/outline_edit_black_24dp';

export const images = {
  uncompleted: CheckBoxOutline,
  completed: CheckBox,
  delete: Delete,
  edit: Edit,
};

이미지 종류별로 컴포넌트를 만들지 않고 IconButton 컴포넌트를 호출 할 때 원하는 이미지의 종류를 props에 type으로 전달하도록 작성한다.

  • 사용자 편의를 위해 버튼 주변을 클릭해서 정확히 클릭 된 것으로 인식하도록 margin을 준다.
  • 만약 pressable 컴포넌트를 사용할 경우 HitRect로 대신 설정할 수 있다.
  • 여기서는 3가지 버튼 종류 중 (TouchableOpacity, TouchableHighlight, Pressable) TouchableOpacity를 사용하지만 다른 버튼을 사용해도 된다.

src/components/IconButton.js

import { images } from '../images';

const IconButton = ({ type }) => {
  return (
    <TouchableOpacity style={styles.iconbutton}>
      <Image source={type} />
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  iconbutton: {
    margin: 10,
  },
});

export default IconButton;

src/App.js

//생략
import IconButton from './components/IconButton';
import { images } from './images';

export default function App() {
  //생략
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Title title="Todo List✔️"></Title>
      <Input
        value={newTask}
        onChangeText={_handleTextChange}
        onSubmitEditing={_addTask}
      />
      <IconButton type={images.uncompleted} />
      <IconButton type={images.completed} />
      <IconButton type={images.delete} />
      <IconButton type={images.edit} />
    </View>
  );
}

결과물

Task 컴포넌트 만들기

  • 할 일 내용은 props로 전달된다.
  • flexDirection: 'row' : 컴포넌트가 옆으로 쌓여야 한다.
  • width: Dimensions.get('window').width - 15 : 넓이는 input 컴포넌트와 동일하게 설정
  • marginLeft: 7 : 예쁘게 만들기 위해 margin 설정
  • alignItems: 'center', justifyContent: 'space-around' : 정렬 설정
  • Text 컴포넌트 : fontSize는 Input 컴포넌트와 동일하게, 예쁘게 정렬하기 위해 flex :1 로 설정해준다.

src/components/Task.js

const Task = ({ text }) => {
  return (
    <View style={styles.container}>
      <IconButton type={images.uncompleted} />
      <Text style={{ fontSize: 20, flex: 1 }}>{text}</Text>
      <IconButton type={images.edit} />
      <IconButton type={images.delete} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    width: Dimensions.get('window').width - 15,
    marginLeft: 7,
    alignItems: 'center',
    justifyContent: 'space-around',
  },
});
  • ScrollView 컴포넌트를 사용해서 할 일 목록을 스크롤을 이용할 수 있도록 화면을 구성한다.
    React Navtive docs : ScrollView
  • 일단은 todo list를 하드코딩해서 화면 UI만 확인한다.

src/App.js

export default function App() {
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Title title="Todo List✔️"></Title>
      <Input
        value={newTask}
        onChangeText={_handleTextChange}
        onSubmitEditing={_addTask}
      />
      <ScrollView>
        <Task text="todo list 1" />
        <Task text="todo list 2" />
        <Task text="todo list 3" />
        <Task text="todo list 4" />
        <Task text="todo list 5" />
      </ScrollView>
    </View>
  );
}

결과물

0개의 댓글