https://jeffgukang.github.io/react-native-tutorial/kr/docs/basic-tutorial/basic-features(todolist)
할 일 목록 앱 만들기
React Native Tutorial For Beginners - 2019
jeffgukang.github.io
개요
RN 기초 공사는 이번주까지만 하고 이제 구현 들어가야 된다.
그렇지만 나는... 아무것도 모르는 깽꺵스인 걸...
일단 뭐라도 굴러가는 걸 만들어보고 싶다.
남은 시간 투두앱에 몰빵해주겠어.
Background
리액트 네이티브를 공부해야겠다! 했을 때 노마드 코더를 좀 깔짝했는데
숏컷으로 끝내기엔 커리큘럼이 꽤나 체계적이고 견고하게 되어 있어서 때려쳤다.
아무튼 인트로만 봤을 때 리액트에 대한 아주 기초적인 지식만 있으면 된다고 하더라.
그것 먼저.
state, props, useEffect, useState
<reference>
https://goddaehee.tistory.com/300?category=395445
[React] 4. React 컴포넌트(2) - 프로퍼티(props)란?
4. React 컴포넌트(2) - 프로퍼티(props)란? 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ React 컴포넌트 내용 중 프로퍼티(props)에 대한 내용 ] 입니다. : ) 간단??? 하게 프로퍼티의 사용 방법을 알아
goddaehee.tistory.com
1. 시작하기
ReactNativeTodos 라는 이름으로 프로젝트 생성해서 에뮬레이터까지 실행한다.
2. 배경색 및 타이틀 변경
튜토리얼을 보고 코드를 따라치는 수준이다 아직은...
대충 하나씩 빼가며 더해가며 어떤 역할 하는지 파악한다.
몇 옵션은 내 입맛대로 바꿔줬는데 이거 은근 재밌네...
import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
} from 'react-native';
const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
color: '#fff',
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
},
});
export default App;
3. Card View 추가하기
이제 안쪽에 모서리가 둥근 스타일의 종이 모양 View를 넣어줄 거라고 한다.
종이 모양을 View로 넣어주고,
그 안에 placeholder를 TextInput으로 넣어준다.
import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput
} from 'react-native';
const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TextInput style={styles.input} placeholder="Add an item!"/>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
color: '#fff',
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
},
card: {
flex: 1,
backgroundColor: '#EEEEDE',
borderTopLeftRadius: 15, // 둥근 모서리
borderTopRightRadius: 15,
marginLeft: 15,
marginRight: 15,
},
input: {
fontSize: 20,
padding: 15, // 간격 띄기
borderBottomColor: '#bbb',
borderBottomWidth: 1, // underline
marginLeft: 20,
marginRight: 20,
},
});
export default App;
4. Scroll View 추가하기
ScrollView를 import 해와서 넣어준다.
// ScrollView는 스크롤 방향 가로, 세로 모두 가능하다.
import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
} from 'react-native';
const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TextInput style={styles.input} placeholder="Add an item!"/>
<ScrollView>
<Text>TodoList</Text>
</ScrollView>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
color: '#fff',
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
},
card: {
flex: 1,
backgroundColor: '#EEEEDE',
borderTopLeftRadius: 15, // 둥근 모서리
borderTopRightRadius: 15,
marginLeft: 15,
marginRight: 15,
},
input: {
fontSize: 20,
padding: 15, // 간격 띄기
borderBottomColor: '#bbb',
borderBottomWidth: 1, // underline
marginLeft: 20,
marginRight: 20,
},
});
export default App;
놀랍게도 스크롤뷰가 적용된 상태다.
왜 Text 사이에 껴주라고 했는지 이해는 안 가지만,
슬쩍 보니 후의 작업을 위한 것 같다.
일단은 이상하게 생겼어도 넘어가도 된다.
5. 컴포넌트 분리하기
이제 행색은 갖췄으니 본격적으로 컴포넌트를 분리해 UI를 만든다고 한다.
여기는 이렇게 나누었다.
- TodoInsert.js : 텍스트 입력창, 추가 버튼
- TodoList.js : 추가된 아이템을 스크롤 뷰로 보여줌.
- TodoListItem.js : 추가된 아이템 하나를 나타내는 부분. 해당 아이템 완료 여부를 나타내는 상태값을 가짐. 완료 체크 이벤트, 삭제 이벤트
이렇게 3개의 파일을 새로운 디렉터리 components 아래에 만들어 넣는다.
6. TodoInsert 컴포넌트
텍스트 입력창과 아이템 추가 버튼 기능.
5.React Native 레이아웃 디자인 - 2부 배치(Flex Direction)와 정렬(justify content, align items)
이 글은 5.React Native 레이아웃 디자인 - 1부 flex와 width, height 5.React Native 레이아웃 디자인 - 2부 배치(Flex Direction)와 정렬(justify content, align items)(현재글) 5.React Native 레이아웃 디자..
yuddomack.tistory.com
// components/TodoInsert.js
import React from 'react';
import {Button, StyleSheet, TextInput, View} from 'react-native';
const TodoInsert = () => {
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Add an item"
placeholderTextColor={'#999'}
autoCorrect={false}
/>
<View style={styles.button}>
{/* ADD 버튼을 넣기 위한 View. 버튼만 넣으면 안 되나? => 버튼 자체의 스타일이 적어서 그런 듯? */}
<Button
title={'ADD'}
color='#3149A8'
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
inputContainer: {
flexDirection: 'row', // 자식 뷰를 어떤 방향으로 배치할지. 디폴트는 column
justifyContent: 'space-between', // 양쪽 정렬
alignItems: 'center',
},
input: {
flex: 1,
padding: 15,
borderBottomColor: '#bbb',
borderBottomWidth: 1,
fontSize: 24,
marginLeft: 20,
},
button: {
marginRight: 10,
},
});
export default TodoInsert;
Button를 넣는데 View를 쓰는 이유가 궁금하다.
적용할 수 있는 스타일이 적어서?
// App.js
import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
} from 'react-native';
import TodoInsert from './components/TodoInsert';
const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
{/* <TextInput style={styles.input} placeholder="Add an item!"/> TodoInsert에서 처리 */}
<TodoInsert />
<ScrollView>
<Text>TodoList</Text>
</ScrollView>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
color: '#fff',
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
},
card: {
flex: 1,
backgroundColor: '#EEEEDE',
borderTopLeftRadius: 15, // 둥근 모서리
borderTopRightRadius: 15,
marginLeft: 15,
marginRight: 15,
},
input: {
fontSize: 20,
padding: 15, // 간격 띄기
borderBottomColor: '#bbb',
borderBottomWidth: 1, // underline
marginLeft: 20,
},
});
export default App;
placeholder 아까 한 걸 왜 또 하지... 했는데 갤 지우고 TodoInsert로 대체하는 거였군.
TouchableOpacity는 스타일 할 수 있는 게 많은 것 같은데
버튼 못생겼다...
7. TodoList 컴포넌트
추가된 아이템을 스크롤 뷰를 통해 보여줌.
// components/TodoList.js
import React from 'react';
import {ScrollView, StyleSheet, Text} from 'react-native';
const TodoList = () => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
<Text>TodoList</Text>
</ScrollView>
);
};
const styles = StyleSheet.create({
listContainer: {
alignItems: 'center',
},
});
export default TodoList;
이건 좀 할 만했다.
// App.js
import React from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
} from 'react-native';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert />
<TodoList />
{/*}
<ScrollView>
<Text>TodoList</Text>
</ScrollView>
*/}
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
color: '#fff',
},
card: {
flex: 1,
backgroundColor: '#EEEEDE',
borderTopLeftRadius: 15, // 둥근 모서리
borderTopRightRadius: 15,
marginLeft: 15,
marginRight: 15,
},
input: {
fontSize: 20,
padding: 15, // 간격 띄기
borderBottomColor: '#bbb',
borderBottomWidth: 1, // underline
marginLeft: 20,
},
});
export default App;
외관상 변화는 별로 없다.
밑줄에 붙어 있던 "TodoList" 가 center로 왔다.
8. TodoListItem 컴포넌트
추가된 아이템 하나를 나타내는 부분이다.
뭔말인가 했더니 TodoList.js 에 import 해줄 애구나.
해당 아이템이 완료 되었는지 아닌지 여부를 나타내는 상태값을 가지게 되고,
완료 체크 이벤트 + 삭제 이벤트 기능을 다룬다.
// component/TodoListItem.js
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
const TodoListItem = () => {
return (
<View style={styles.container}>
<TouchableOpacity>
<View style={styles.circle} />
</TouchableOpacity>
<Text style={styles.text}>TodoList items will be shown here</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
borderBottomColor: '#bbb',
borderBottomWidth: StyleSheet.hairlineWidth,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
text: { // 아이템
flex: 5,
fontWeight: '500',
fontSize: 18,
marginVertical: 20,
width: 100,
},
circle: { // 체크 버튼
width: 30,
height: 30,
borderRadius: 15,
borderColor: 'gray',
borderWidth: 2,
marginRight: 20,
marginLeft: 20,
},
});
export default TodoListItem;
// components/TodoList.js
import React from 'react';
import {ScrollView, StyleSheet, Text} from 'react-native';
import TodoListItem from './TodoListItem';
const TodoList = () => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
<TodoListItem />
{/* <Text>TodoList</Text> */}
</ScrollView>
);
};
const styles = StyleSheet.create({
listContainer: {
alignItems: 'center',
},
});
export default TodoList;
8. 아이콘 추가하기
TodoListItem 컴포넌트 안에는 사용자가 입력한 할 일 목록 내용이 담긴다.
해당 목록을 완료했는지 체크할 수 있는 체크 박스와 삭제 버튼을 추가하자.
터미널에
yarn add react-native-vector-icons
...를 입력해주면 된다.
참고로, 나는 실수로 'react-native-icons'라고 해서 설치하는 바람에 안드로이드 빌드 오류가 났다.
https://github.com/oblador/react-native-vector-icons/blob/master/README.md#android
GitHub - oblador/react-native-vector-icons: Customizable Icons for React Native with support for image source and full styling.
Customizable Icons for React Native with support for image source and full styling. - GitHub - oblador/react-native-vector-icons: Customizable Icons for React Native with support for image source a...
github.com
그래서 여기 기웃기웃 거리면서 하라는 대로 해도 안 됐다.
설마 너... 아까 그거 때문에 그런 거야? 싶어서
yarn remove react-native-icons
...해주고 node-modules을 지워서 다시 yarn 해주니 돌아간다. 하하...
참고 사항 1) 튜토리얼에 나오는 링크 과정은 최신 버전은 필요 없다.
참고 사항 2) 아이콘 적용 하려면 다시 프로젝트를 컴파일 해줘야 한다고... // gradle 로 바로 처리하는 방법도 있음.
// component/TodoListItem.js
import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/AntDesign'
const TodoListItem = () => {
return (
<View style={styles.container}>
{/* 해결 동그라미 */}
<TouchableOpacity>
<View style={styles.completeCircle}>
<Icon name="circledowno" size={30} color="#3149A8" />
</View>
</TouchableOpacity>
<Text style={[styles.text, styles.strikeText]}>
Items will be shown here
</Text>
{/* 삭제 버튼 */}
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>
<Icon name="delete" size={30} color="#cc0000" />
</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
borderBottomColor: '#bbb',
borderBottomWidth: StyleSheet.hairlineWidth,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
text: { // 아이템
flex: 5,
fontWeight: '500',
fontSize: 18,
marginVertical: 20,
width: 100,
},
circle: { // 미헤결 동그라미
width: 30,
height: 30,
borderRadius: 15,
borderColor: 'gray',
borderWidth: 2,
marginRight: 20,
marginLeft: 20,
},
completeCircle: { // 해결 동그라미
marginRight: 20,
marginLeft: 20,
},
strikeText: { // 줄 그어진 텍스트
color: '#bbb',
textDecorationLine: 'line-through',
},
unstrikeText: { // 그냥 텍스트
color: '#29323c',
},
buttonContainer: { // 삭제 버튼
marginVertical: 10,
marginHorizontal: 10,
},
});
export default TodoListItem;
10. 상태(state) 추가하기
할 일 목록 추가, 삭제 기능 등을 구현하기 위해서는 상태값을 정의해야 한다.
todos 라는 속성으로 상태를 정의해서 할 일 목록의 상태를 관리한다.
todos: {id: Number, textValue: string, checked: boolean }
- id: 각 목록 고유 아이디
- textValue: 목록 내용
- checked: 완료 여부
react (그리고 react-native)에서는 커포넌트를 작성하는 방법이
1) 함수형 컴포넌트, 2) 클래스형 컴포넌트
이렇게 있다.
요즘엔 함수형 컴포넌트로 작성되는 게 권장된다는 얘기도 있고, 내 프로젝트에서는 함수형 컴포넌트로 작성할 거다.
둘의 가장 큰 차이점이 생명 주기 함수 이용 여부다.
state는 생명 주기 함수...로 lifecycle을 관리해줘야 하고, 원래는 클래스 컴포넌트에서만 쓸 수 있다.
그런데, 리액트 버전 16.8부터는 hook(훅)이 추가되었다.
이를 이용하면 함수형 컴포넌트에서도 컴포넌트의 상태값을 관리할 수 있고, 생명 주기 함수를 이용할 수 있다.
결론은, hook을 이용해 state를 관리해주겠다는 소리!
// App.js
import React, {useState} from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
} from 'react-native';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
const [Todos, setTodos] = useState([]);
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert />
<TodoList />
{/*}
<ScrollView>
<Text>TodoList</Text>
</ScrollView>
*/}
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1, // 화면 전체에서 1의 비율
backgroundColor: '#3149A8',
},
appTitle: {
fontSize: 36,
marginTop: 30, // 문구 위에 마진
marginBottom: 30, // 문구 아래 마진... 너무 크면 문구가 사라짐
fontWeight: '30', // 폰트 굵기. 근데 왜 문자열로 처리하지?
textAlign: 'right', // 정렬
backgroundColor: '#3149A8',
color: '#fff',
},
card: {
flex: 1,
backgroundColor: '#EEEEDE',
borderTopLeftRadius: 15, // 둥근 모서리
borderTopRightRadius: 15,
marginLeft: 15,
marginRight: 15,
},
input: {
fontSize: 20,
padding: 15, // 간격 띄기
borderBottomColor: '#bbb',
borderBottomWidth: 1, // underline
marginLeft: 20,
},
});
export default App;
.
.
.
useState를 어디에 넣어줘야 되는지 몰라서 헤맸다...
App 안에 넣어주면 되는 거였군.
11. 할 일 목록 추가하기
1) setTodos 를 이용해 할 일 목록을 추가하는 addTodo 함수를 만든다.
2) props를 이용해 App.js에서 TodoInsert.js 로 데이터를 전달한다. (addTodo 함수 전달)
// App.js
import React, {useState} from 'react';
import type {Node} from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
View,
TextInput,
ScrollView,
} from 'react-native';
import TodoInsert from './components/TodoInsert';
import TodoList from './components/TodoList';
const App = () => {
const [todos, setTodos] = useState([]);
const addTodo = text => { // 할 일 목록을 추가하는 함수
setTodos([
...todos,
{id: Math.random().toString(), textValue: text, checked: false},
]);
};
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert onAddTodo={addTodo} />
{/* props를 이용해 컴포넌트 간 데이터 전달 addTodo를 TodoInsert에 */}
<TodoList />
</View>
</SafeAreaView>
);
};
...
속성으로 전달 함수는 TodoInsert 컴포넌트에서 아래와 같이 받아올 수 있다.
//components/TodoInsert.js
...
const TodoInsert = ({onAddTodos}) => {
...
}
...
TodoInsert에서 TextInput으로 텍스트를 받아올 것이다.
그럼 텍스트 값의 상태 관리가 필요하다.
1) 텍스트를 입력할 때마다 최신으로 텍스트 값을 업데이트 해줄 것 (todoInputHandler)
2) ADD 버튼을 누르면 아이템을 추가해줄 것 (addTodoHandler)
// components/TodoInsert.js
import React, {useState} from 'react';
import {Button, StyleSheet, TextInput, View} from 'react-native';
const TodoInsert = ({onAddTodo}) => {
const [newTodoItem, setNewTodoItem] = useState(''); // 사용자가 입력한 텍스트 값의 상태 관리
const todoInputHandler = newTodo => { // 실시간으로 사용자가 입력한 텍스트 값의 변화를 관리하기 위한 핸들러 함수
setNewTodoItem(newTodo);
};
const addTodoHandler = () => { // 아이템을 추가해주는 핸들러 함수
onAddTodo(newTodoItem); // 사용자가 입력한 텍스트 값을 전달 받아 목록에 추가
setNewTodoItem(''); // 입력창을 공백으로 초기화하는 역할
};
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Add an item"
placeholderTextColor={'#999'}
onChangeText={todoInputHandler}
// 입력창에 텍스트를 입력하면 실시간으로 입력한 텍스트 값의 상태가 업데이트
value={newTodoItem}
// newTodoItem에 텍스트 값의 최신 상태가 담김
autoCorrect={false}
/>
<View style={styles.button}>
{/* ADD 버튼을 넣기 위한 View. 버튼만 넣으면 안 되나? => 버튼 자체의 스타일이 적어서 그런 듯? */}
<Button
title={'ADD'}
color='#3149A8'
onPress={addTodoHandler}
/>
</View>
</View>
);
};
...
완료!
이제 추가한 아이템을 리스트에 출력해야 하는데 그러려면 일단 TodoList.js, TodoListItem.js에 App.js에 있는 todos 객체를 전달해야 한다.
주의할 점.
todos는 할 일 목록의 객체가 담긴 배열이다. 그래서 TodoList.js에서 TodoListItem.js로 전달 할 때 배열에 담긴 객체 하나하나를 넘겨줘야 한다.
먼저 App.js에서 TodoList.js으로 todos로 전달.
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert onAddTodo={addTodo} />
{/* props를 이용해 컴포넌트 간 데이터 전달 addTodo를 TodoInsert에 */}
<TodoList todos={todos} />
{/* props를 이용해 todos를 TodoList에 */}
</View>
</SafeAreaView>
);
};
TodoList에서 todos를 받고 이 할 일 목록 배열 객체에서 할 일 목록 하나씩 TodoListItem로 전달.
// components/TodoList.js
import React from 'react';
import {ScrollView, StyleSheet, Text} from 'react-native';
import TodoListItem from './TodoListItem';
const TodoList = ({todos}) => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
{todos.map(todo => ( // map 함수로 todos에 담긴 아이템을 하나씩 전달
<TodoListItem {...todo} />
// js destructuring 문법. TodoListItem 컴포넌트에서 아이템 객체에 담긴 값을 바로 받음.
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
listContainer: {
alignItems: 'center',
},
});
export default TodoList;
나도 destructuring이 뭔지는 잘 모른다 ㅠ.ㅠ
<reference>
https://poiemaweb.com/es6-destructuring
Destructuring | PoiemaWeb
디스트럭처링(Destructuring)은 구조화된 객체(배열 또는 객체)를 Destructuring(비구조화, 파괴)하여 개별적인 변수에 할당하는 것이다. 배열 또는 객체 리터럴에서 필요한 값만을 추출하여 변수에 할
poiemaweb.com
참고.
각 아이템에는 textValue, id, checked 라는 key, 그리고 그에 해당하는 value가 담겨 있다.
다음처럼 TodoListItem에서 TodoList에서 전달한 값을 받을 수 있다.
...
const TodoListItem = ({textValue, id, checked}) => {
return (
<View style={styles.container}>
{/* 해결 동그라미 */}
<TouchableOpacity>
<View style={styles.completeCircle}>
<Icon name="circledowno" size={30} color="#3149A8" />
</View>
</TouchableOpacity>
<Text style={[styles.text, styles.strikeText]}>{textValue}</Text>
{/* 삭제 버튼 */}
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText}>
<Icon name="delete" size={30} color="#cc0000" />
</Text>
</TouchableOpacity>
</View>
);
};
...
이제 입력창에 텍스트를 입력하고 ADD 버튼을 눌러 추가하면 리스트에 아이템이 추가된다.
근데 시뮬레이터 하단에 경고창이 뜬다는데 나는 안 뜬다. ^^
대충 id가 겹쳐서 경고가 떠야 하는데 랜덤이라서 내 에뮬은 아직 안 겹쳤나 보다...
그래서 여기서는 id를 대충 Math.random() 로 해줬지만, 각각 고유의 키를 가져야 하므로
실전에서는 uuid 같은 패키지를 이용하라는 것.
그래서 이렇게 수정해주란다.
...
const TodoList = ({todos}) => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
{todos.map(todo => ( // map 함수로 todos에 담긴 아이템을 하나씩 전달
<TodoListItem key={todo.id} {...todo} />
// js destructuring 문법. TodoListItem 컴포넌트에서 아이템 객체에 담긴 값을 바로 받음.
))}
</ScrollView>
);
};
...
12. 할 일 목록 삭제
이제 삭제하는 기능을 구현해주자.
App.js에 onRemove를 생성.
그리고 TodoList에 이를 전달.
...
const App = () => {
const [todos, setTodos] = useState([]);
const addTodo = text => { // 할 일 목록을 추가하는 함수
setTodos([
...todos,
{id: Math.random().toString(), textValue: text, checked: false},
]);
};
const onRemove = id => e => {
setTodos(todos.filter(todo => todo.id !== id)) // 해당 아이디를 제외하고 새로운 배열을 만드는 함수
수 };
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert onAddTodo={addTodo} />
{/* props를 이용해 컴포넌트 간 데이터 전달 addTodo를 TodoInsert에 */}
<TodoList todos={todos} onRemove={onRemove} />
{/* props를 이용해 todos를 TodoList에 */}
</View>
</SafeAreaView>
);
};
...
TodoList에서 전달 받는다.
const TodoList = ({todos, onRemove}) => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
{todos.map(todo => ( // map 함수로 todos에 담긴 아이템을 하나씩 전달
<TodoListItem key={todo.id} {...todo} onRemove={onRemove}/>
// js destructuring 문법. TodoListItem 컴포넌트에서 아이템 객체에 담긴 값을 바로 받음.
))}
</ScrollView>
);
};
TodoListItem에서도 전달 받고, 버튼을 활성화 해준다.
의문. 근데 왜 Onpress 옵션을 텍스트에다 걸지?
...
const TodoListItem = ({textValue, id, checked, onRemove}) => {
return (
<View style={styles.container}>
{/* 해결 동그라미 */}
<TouchableOpacity>
<View style={styles.completeCircle}>
<Icon name="circledowno" size={30} color="#3149A8" />
</View>
</TouchableOpacity>
<Text style={[styles.text, styles.strikeText]}>{textValue}</Text>
{/* 삭제 버튼 */}
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText} onPress={onRemove(id)}>
<Icon name="delete" size={30} color="#cc0000" />
</Text>
</TouchableOpacity>
</View>
);
};
...
이제 삭제 가능.
13. 할 일 목록 완료 체크 하기
이제 마지막으로 완료 체크하는 기능만 구현하면 된다.
각 목록의 왼쪽에 있는 토클 버튼을 누르면 체크 표시가 디고 한 번 더 누르면 체크가 해제되도록 구현하겠다.
// 그걸 토글이라고 하는구나.
App.js에 onToggle 함수를 만들어주고, 이걸 TodoList에 전달
...
const onToggle = id => e => { // 아이템의 id를 받아와서 해당하는 아이템의 checked 속성값을 반대로 변경.
setTodos(
todos.map(todo =>
todo.id === id ? {...todo, checked: !todo.checked} : todo,
),
);
};
return (
<SafeAreaView style={styles.container}>
{/* 스마트폰 노치 문제 해결 */}
<Text style={styles.appTitle}>TodoList </Text>
{/* 앱 타이틀 */}
<View style={styles.card}>
{/* 내부 카드 */}
<TodoInsert onAddTodo={addTodo} />
{/* props를 이용해 컴포넌트 간 데이터 전달 addTodo를 TodoInsert에 */}
<TodoList todos={todos} onRemove={onRemove} onToggle={onToggle} />
{/* props를 이용해 todos를 TodoList에 */}
{/*}
<ScrollView>
<Text>TodoList</Text>
</ScrollView>
*/}
</View>
</SafeAreaView>
);
};
...
TodoList에서 이걸 받아서 TodoListItem에 전달
const TodoList = ({todos, onRemove, onToggle}) => {
return(
<ScrollView contentContainerStyle={styles.listContainer}>
{todos.map(todo => ( // map 함수로 todos에 담긴 아이템을 하나씩 전달
<TodoListItem key={todo.id} {...todo} onRemove={onRemove} onToggle={onToggle} />
// js destructuring 문법. TodoListItem 컴포넌트에서 아이템 객체에 담긴 값을 바로 받음.
))}
</ScrollView>
);
};
TodoListItem에서 받는다.
그리고 onPressout 이벤트를 넣어서 checked가 되어 있으면 completeCircle가 보이게 하고, 되어 있지 않으면 circle이 보이게 한다.
Text 영역도 똑같이 checked를 확인해서 되어 있으면 strikeText를 적용하고 아니면 unstrikeText를 적용한다.
// onPressOut 이벤트는 onPress가 끝나고 바로 진행되는 이벤트다. 버튼 터치가 끝날 때마다 검사하는 것.
...
const TodoListItem = ({textValue, id, checked, onRemove, onToggle}) => {
return (
<View style={styles.container}>
{/* 해결 동그라미 */}
<TouchableOpacity onPressOut={onToggle(id)}>
{checked ? (
<View style={styles.completeCircle}>
<Icon name="circledowno" size={30} color="#3149A8" />
</View>
) : (
<View style={styles.circle} />
)}
</TouchableOpacity>
<Text // Text 영역
style={[
styles.text,
checked ? styles.strikeText : styles.unstrikeText,
]}>
{textValue}
</Text>
{/* 삭제 버튼 */}
<TouchableOpacity style={styles.buttonContainer}>
<Text style={styles.buttonText} onPress={onRemove(id)}>
<Icon name="delete" size={30} color="#cc0000" />
</Text>
</TouchableOpacity>
</View>
);
};
...
여기까지 Basic 한 TodoList 구현은 끝.
'Dev > React Native' 카테고리의 다른 글
React Native Setup Android Dev Environment on MacOS && Troubleshooting (0) | 2022.08.12 |
---|---|
React Native Tutorial For Beginners - Basic (0) | 2022.08.11 |