@ List
* 4.1 불변성을 지키면서 객체와 배열 업데이트하기
** 4.1.1 불변성을 지켜야 하는 이유
** 4.1.2 배열의 불변성을 지키는 방법
* 4.2 todos 상태 만들기 및 FlatList로 항목 화면에 나타내기
** 4.2.1 todos 상태 만들기
** 4.2.2 TodoList 컴포넌트 만들기
** 4.2.3 TodoItem 컴포넌트 만들기
** 4.2.4 항목 사이에 구분선 보여주기
** 4.2.5 완료한 항목에 다른 스타일 적용하기
@ Note
1. 렌더링 성능 최적화 방식이기 때분에 불변성 지킬 것
- 항목 추가 시 : spread 연산자, concat()
> const numbers = [0, 1, 2, 3];
const nextnumbers = [...number, 4]; // [0, 1, 2, 3, 4]
const nextnumbers = numbers.concat(4); // [0, 1, 2, 3, 4]
- 항목 제거 시 : filter()
> const numbers = [-3, -2, -1, 0, 1, 2, 3];
const filtered = numbers.filter(number => number > 0); // [1, 2, 3]
const nextNumbers = numbers.filter(number => number !== 0); // [-3, -2, -1, 1, 2, 3]
const nextNumbers = numbers.filter((number, i) => i !== 3); // [-3, -2, -1, 1, 2, 3]
- 항목 수정 시 : map()
> const numbers = [-3, -2, -1, 0, 1, 2, 3];
const nextNumbers = numbers.map(number => number === 0 ? 10 : number); // [-3, -2, -1, 10, 1, 2, 3]
2. style 적용 시 배열로 여러개 적용 가능
- 배열 순서에 영향을 받으나, 'done &&' 조건으로 선택적 적용 가능
- ex) <View style={[styles.circle, done && styles.filled]}>
@ Result
# Source tree
# App.js
import React, {useState} from 'react';
import {StyleSheet, KeyboardAvoidingView, Platform} from 'react-native';
import DateHead from './components/DateHead';
// for IOS StatusBar
import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context';
import AddTodo from './components/AddTodo';
import Empty from './components/Empty';
import TodoList from './components/TodoList';
function App() {
const today = new Date();
const [todos, setTodos] = useState([
{id: 1, text: '작업환경 설정', done: true},
{id: 2, text: '리액트 네이티브 기초 공부', done: false},
{id: 3, text: '투두리스트 만들어보기', done: false},
]);
return (
<SafeAreaProvider>
<SafeAreaView edges={['bottom']} style={styles.block}>
<KeyboardAvoidingView
// behavior={Platform.OS === 'ios' ? 'padding' : undefined} // 삼항연산자 이용 시
behavior={Platform.select({ios: 'padding', android: undefined})}
style={styles.avoid}>
<DateHead date={today} />
{todos.length === 0 ? <Empty /> : <TodoList todos={todos} />}
<AddTodo />
</KeyboardAvoidingView>
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
block: {
flex: 1,
backgroundColor: 'white',
},
avoid: {
flex: 1,
},
});
export default App;
# components > AddTodo.js
import React, {useState} from 'react';
import {
View,
StyleSheet,
TextInput,
Image,
Platform,
TouchableOpacity,
TouchableNativeFeedback,
Keyboard,
} from 'react-native';
function AddTodo() {
const [text, setText] = useState('');
const onPress = () => {
setText('');
Keyboard.dismiss(); // button 눌렀을 시 키보드 사라짐
};
const button = (
<View style={styles.buttonStyle}>
<Image source={require('../assets/icons/add_white/add_white.png')} />
</View>
);
return (
<View style={styles.block}>
<TextInput
placeholder="할일을 입력하세요."
style={styles.input}
value={text}
onChangeText={setText}
onSubmitEditing={onPress} // Enter 눌렀을 시 키보드 사라짐
returnKeyType="done" // Enter 타입 지정
/>
{/* 삼항연산자 이용 */}
{/* {Platform.OS === 'ios' ? (
// ios : 터치 시 투명도 변경
<TouchableOpacity activeOpacity={0.5}>
<View style={styles.buttonStyle}>
<Image
source={require('../assets/icons/add_white/add_white.png')}
/>
</View>
</TouchableOpacity>
) : (
// android : 터치 시 물결
<View style={styles.circleWrapper}>
<TouchableNativeFeedback>
<View style={styles.buttonStyle}>
<Image
source={require('../assets/icons/add_white/add_white.png')}
/>
</View>
</TouchableNativeFeedback>
</View>
)} */}
{Platform.select({
ios: <TouchableOpacity onPress={onPress}>{button}</TouchableOpacity>,
android: (
<View style={styles.circleWrapper}>
<TouchableNativeFeedback onPress={onPress}>
{button}
</TouchableNativeFeedback>
</View>
),
})}
</View>
);
}
const styles = StyleSheet.create({
block: {
backgroundcolor: 'white',
height: 64,
paddingHorizontal: 16, // 좌우 여백
bordercolor: '#bdbdbd',
borderTopWidth: 1,
borderBottomWidth: 1,
alignItems: 'center', //상하 정렬
flexDirection: 'row',
},
input: {
flex: 1, // TextInput 란 확장
fontSize: 16,
paddingVertical: 8, // 상하 터치영역 확장
},
buttonStyle: {
alignItems: 'center',
justifyContent: 'center',
width: 48,
height: 48,
backgroundColor: '#26a69a',
borderRadius: 24,
},
circleWrapper: {
overflow: 'hidden', // 지정한 영역 외 바깥 영역 숨김
borderRadius: 24,
},
});
export default AddTodo;
# components > DataHead.js
import React from 'react';
import {View, Text, StyleSheet, StatusBar} from 'react-native';
// for IOS StatusBar
import {useSafeAreaInsets} from 'react-native-safe-area-context';
function DateHead() {
const d = new Date();
const year = d.getFullYear();
const month = d.getMonth() + 1; // getMonth 범위 : 0 ~ 11 까지
const day = d.getDate();
const formatted = `${year}년 ${month}월 ${day}일`;
const {top} = useSafeAreaInsets();
return (
<>
<View style={[styles.statusBarPlaceholder, {height: top}]} />
<StatusBar backgroundColor="#26a69a" barStyle="light-content" />
<View style={styles.block}>
{/* <Text style={styles.dateText}>{formatted}</Text> */}
<Text style={styles.dateText}>
{year}년 {month}월 {day}일
</Text>
</View>
</>
);
}
const styles = StyleSheet.create({
statusBarPlaceholder: {
backgroundColor: '#26a69a',
},
block: {
padding: 16,
backgroundColor: '#26a69a',
},
dateText: {
fontSize: 24,
color: 'white',
},
});
export default DateHead;
# components > Empty.js
import React from 'react';
import {View, Text, Image, StyleSheet} from 'react-native';
function Empty() {
// const source = {uri: 'https://via.placeholder.com/150'};
return (
<View style={styles.block}>
{/* <Image
source={require('../assets/images/circle.png')}
style={styles.image}
resizeMode="cover"
/> */}
{/* <Image source={source} style={styles.image} resizeMode="cover" /> */}
<Image
source={require('../assets/images/young_and_happy.png')}
style={styles.image}
resizeMode="cover"
/>
<Text style={styles.description}>할일이 없습니다.</Text>
</View>
);
}
const styles = StyleSheet.create({
block: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
image: {
width: 240,
height: 179,
marginBottom: 16,
},
description: {
fontSize: 24,
color: '#9e9e9e',
},
});
export default Empty;
# components > TodoItem.js
import React from 'react';
import {View, Text, StyleSheet, Image} from 'react-native';
function TodoItem({id, text, done}) {
return (
<View style={styles.item}>
{/* <View style={styles.circle} />
<Text style={styles.text}>{text}</Text> */}
<View style={[styles.circle, done && styles.filled]}>
{done && (
<Image
source={require('../assets/icons/check_white/check_white.png')}
/>
)}
</View>
<Text style={[styles.text, done && styles.lineThrough]}>{text}</Text>
</View>
);
}
const styles = StyleSheet.create({
item: {
flexDirection: 'row',
padding: 16,
alignItems: 'center',
},
circle: {
width: 24,
height: 24,
borderRadius: 12,
borderWidth: 1,
borderColor: '#26a69a',
marginRight: 16,
},
filled: {
alignItems: 'center',
backgroundColor: '#26a69a',
},
text: {
flex: 1,
fontSize: 16,
color: '#212121',
},
lineThrough: {
color: '#9e9e9e',
textDecorationLine: 'line-through',
},
});
export default TodoItem;
# components > TodoList.js
import React from 'react';
import {FlatList, View, Text, StyleSheet} from 'react-native';
import TodoItem from './TodoItem';
function TodoList({todos}) {
return (
<FlatList
ItemSeparatorComponent={() => <View style={styles.separator} />}
style={styles.list}
data={todos}
renderItem={({item}) => (
// <View>
// <Text>{item.text}</Text>
// </View>
<TodoItem id={item.id} text={item.text} done={item.done} />
)}
keyExtractor={item => item.id.toString()}
/>
);
}
const styles = StyleSheet.create({
list: {
flex: 1,
},
separator: {
backgroundColor: '#e0e0e0',
height: 1,
},
});
export default TodoList;
'React Native > React Native_study' 카테고리의 다른 글
[리액트 네이티브를 다루는 기술 #7] 5장 리액트 내비게이션으로 여러 화면 관리하기 (p.215 ~ 243) (0) | 2021.12.27 |
---|---|
[리액트 네이티브를 다루는 기술 #6] 4장 할일 목록 만들기2 (p.179 ~ 214) (0) | 2021.12.24 |
[리액트 네이티브를 다루는 기술 #4] 3장 할일 목록 만들기1 (p.133 ~ 160) (1) | 2021.12.22 |
[리액트 네이티브를 다루는 기술 #3] 3장 할일 목록 만들기1 (p.109 ~ 132) (0) | 2021.12.20 |
[리액트 네이티브를 다루는 기술 #2] 2장 컴포넌트 (p.99 ~ 108) (1) | 2021.12.20 |