개인노트 nomad React-Native 초급 #3 Work hard Travel hard App
[2021 UPDATE] WORK HARD TRAVEL HARD APP
# 3.0 Introduction
## Expo 프로젝트 셋업
expo init WorkHardTravelHardApp --npm
blank선택
## 참고 디자인
https://dribbble.com/shots/5985329-Do-More-Task-List
# 3.1 Touchables
## 수평 방향에 padding 적용하기
paddingHorizontal : 수평 방향에 padding 적용하기
paddingVertical : 수직방향에 padding 적용하기
css에는 없는 속성입니다.
## button (Touchable 컴포넌트)
버튼에는 매우 흥미로운 3가지 컴포넌트들이 있습니다.
1. TouchableOpacity
View와 비슷한 종류(box류)라고 볼 수 있고, 누르는 이벤트를 listen할 준비가 된 view라고 할 수 있습니다.
Opacity가 있는 이유는 여기에 애니메이션 효과가 있기 때문입니다. 탭(click)하면(하고 있는 동안) 누른 요소를 약간 투명해지게 만듭니다.
<TouchableOpacity>
<Text style={styles.btnText}>Work</Text>
</TouchableOpacity>
2. TouchableHighlight
TouchableHighlight는 요소를 클릭했을 때 배경색이 바뀌도록 해줍니다.
TouchableHighlight는 TouchableOpacity보다 더 많은 속성을 가지고 있습니다.
3. TouchableWithoutFeedback
TouchableWithoutFeedback은 Touchable 컴포넌트인데
화면의 가장 위에서 일어나는 탭 이벤트를 listen합니다. 하지만 그래픽이나 다른 UI 반응을 보여주지 않습니다. 즉, 어플리케이션의 생김새는 변하는게 없습니다.
### 속성
onPress : 유저가 Touchable을 눌렀을 때 실행되는 이벤트를 말합니다.
onPressIn : 손가락이 그 영역 안에 들어갈 때를 말합니다.
onPressOut : 손가락이 그 영역에서 벗어날 때를 말합니다.
onLongPress : 손가락이 영역에 들어가서 오랫동안 머무를 때를 말합니다.
※ 보통의 클릭 이벤트를 구현한다면, press속성들에서 직접 in과 out을 특정지을 필요는 없습니다. 그냥 press를 특정지으면 됩니다. 그냥 press는 손가락이 들어갔다 나오는 바로 그 행위를 말합니다.
## Pressable 컴포넌트
Pressable 은 TouchableWithoutFeedback과 같지만 좀 더 새로운 컴포넌트입니다.
Pressable 은 더 많은 설정을 줄 수 있습니다.
만약 더 확장성있고 미래에도 사용가능한 터치기반의 input을 다루길 원한다면 TouchableOpacity대신 Pressable을 고려해 볼 만 합니다.
### 속성
delayLongPress : 얼마나 길게 누르면 반응하게 만들지 설정할 수 있습니다.
hitSlop : 요소 바깥 어디까지 탭 누르는 것을 감지할지 정할 수 있습니다.
## 정리
TouchableOpacity
View가 터치에 적절하게 반응하도록 하는 래퍼.
아래로 누르면 래핑된 View의 opacity가 감소하여 흐리게 표시됩니다.
https://docs.expo.dev/versions/v44.0.0/react-native/touchableopacity/
TouchableHighlight
View가 터치에 적절하게 반응하도록 하는 래퍼.
아래로 누르면 래핑된 View의 background를 표시합니다.
https://docs.expo.dev/versions/v44.0.0/react-native/touchablehighlight/
TouchableWithoutFeedback
합당한 이유가 없는 한 사용하지 마십시오.
Press에 반응하는 모든 요소는 만졌을 때 시각적 피드백이 있어야 합니다.
https://docs.expo.dev/versions/v44.0.0/react-native/touchablewithoutfeedback/
Pressable
Pressable은 정의된 자식에 대한 다양한 Press 상호 작용 단계를 감지할 수 있는 핵심 구성 요소 래퍼입니다.
https://docs.expo.dev/versions/v44.0.0/react-native/pressable/
# 3.2 TextInput
키보드를 통해 앱에 텍스트를 입력하기 위한 기본 구성 요소입니다.
https://docs.expo.dev/versions/v44.0.0/react-native/textinput/
## props
1. onFocus
2. onChangeText
3. keyboardType
Keyboard Type 이미지 보기
https://lefkowitz.me/visual-guide-to-react-native-textinput-keyboardtype-options/
4. multiline
5. placeholder
6. placeholderTextColor
# 3.3 To Dos
## Hashmap
참고 영상 :
개발자라면 꼭 알아야할 Hash Table 의 모든 것!
https://www.youtube.com/watch?v=HraOg7W3VAM
### Hash Table 참고 영상 정리
Hash Tables 은 Key Value System 을 이용하여 자료를 정리합니다.
Key Value System
{key : value}
#### Hash Tables 와 Array (배열) 의 비교
1. 배열
menu = [
{ name: "coffee", price: 10 },
{ name: "burger", price: 15 },
{ name: "tea", price: 5 },
{ name: "pizza", price: 10 },
{ name: "juice", price: 5 },
];
pizza를 찾으려면 Linear Search (선형 검색)을 하면 됨.
각각의 아이템을 처음부터 접근하여 찾아 갑니다.
이건 시간이 너무 오래 걸립니다.
그래서 이 때! Hash Tables 를 이용합니다.
이것을 Hash Tables 로 정리한다면?
2. Hash Tables
menu = {
coffee: 10,
burger: 15,
tea: 5,
pizza: 10,
juice: 5,
};
pizza를 찾으려면 pizza를 키로 찾습니다.
menu["pizza"] // = 10
#### 시간복잡도
1. 배열
O(N)
Linear Time (선형 시간)
아이템이 많을수록 찾는 시간이 오래걸립니다.
2. Hash Tables
O(1)
Constant Time (상수 시간)
어떤 메뉴의 값을 찾더라도 소요되는 건 딱 1개 스텝입니다.
아이템을 추가할때도 삭제할때도 마찬가지로 O(1) 입니다.
Array 랑 비교 했을 때 매우 빠릅니다.
#### 좀 더 복잡한 Hash Tables 다루기
key 와 value 중 value만 저장하는 테크닉을 보겠습니다.
나라들 = {
"그리스" : true,
"네덜란드" : true,
"태국" : true,
"한국" : true,
"터키" : true,
"이탈리아" : true,
}
이렇게 하면 태국이 리스트에 있는지 찾는 데 딱 1번의 스텝만 필요합니다.
나라들["태국"]; // true
나라들["서울"] // undefined
#### Hash Tables 의 원리
내부에는 array 같은 구조가 있습니다.
하지만 array와 다르게 빠른 이유는 Hash Function (해시 함수) 때문입니다.
Hash Function은 저장하고 싶은 key를 숫자로 바꿉니다. 그 숫자는 바로 array의 index가 됩니다.
#### 정리
Hash Tables은 Array처럼 리스트가 있지만 시간복잡도는 O(1) 인 것입니다.
하지만 Hash Tables의 시간복잡도가 언제나 상수 시간인 것은 아닙니다. 왜냐하면 Collision(해시 충돌)이 있을 수 있고, 그 경우에는 선형 검색을 하기 때문입니다.
javascript 에서는 Hash Tables을 object로 구현합니다.
## object assign
object를 가져다가 다른 object와 합쳐줍니다.
코드 예
const newToDos = Object.assign({}, toDos, {
[Date.now()]: { text, work: working },
});
setToDos(newToDos);
# 3.4 Paint To Dos
## Object.keys(x)
Array를 사용하지 않고 위처럼 hash tables를 사용했을 경우, map함수를 사용해서 jsx에서 출력하기 위해서 Object.keys(x)를 사용할 수 있습니다.
Object.keys(x) 를 사용해서 object안 key들을 array로 얻어내고, 이것을 map으로 출력합니다.
코드 예
<ScrollView>
{Object.keys(toDos).map((key) => (
<View key={key}>
<Text>{toDos[key].text}</Text>
</View>
))}
</ScrollView>
# 3.5 Persist
앱에 저장하여 사용자가 다른 앱으로 이동하거나 앱을 닫고 다시 돌아와도 내용을 유지하게 합니다.
string만 저장가능합니다. object를 저장하려면 먼저 object를 string으로 바꿔주어야합니다.
브라우저의 local storage처럼 작동합니다. 단, await를 사용해야합니다.
## AsyncStorage
expo에는 AsyncStorage 라는 module이 있습니다.
Async Storage는 문자열 데이터만 저장할 수 있으므로 객체 데이터를 저장하려면 먼저 직렬화해야 합니다.
JSON으로 직렬화할 수 있는 데이터의 경우 데이터를 저장할 때 JSON.stringify()를 사용하고 데이터를 로드할 때 JSON.parse()를 사용할 수 있습니다.
expo install @react-native-async-storage/async-storage
### 참고 url
https://docs.expo.dev/versions/v44.0.0/sdk/async-storage/
https://react-native-async-storage.github.io/async-storage/docs/usage/
### 참고
expo install은 기본적으로 npm install을 실행시킵니다.
expo install을 사용하면 현재 사용중인 expo 버전과 같은 버전의 모듈을 설치해줍니다.
### 사용
#### Storing data
1. Storing string value
import AsyncStorage from '@react-native-async-storage/async-storage';
// ...
const storeData = async (value) => {
try {
await AsyncStorage.setItem('@storage_Key', value)
} catch (e) {
// saving error
}
}
2. Storing object value
import AsyncStorage from '@react-native-async-storage/async-storage';
// ...
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('@storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
#### Reading data
1. Reading string value
const getData = async () => {
try {
const value = await AsyncStorage.getItem('@storage_Key')
if(value !== null) {
// value previously stored
}
} catch(e) {
// error reading value
}
}
2. Reading object value
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('@storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
### 전체코드
import { StatusBar } from "expo-status-bar";
import {
StyleSheet,
Text,
View,
TouchableOpacity,
TouchableHighlight,
TouchableWithoutFeedback,
TextInput,
ScrollView,
} from "react-native";
import { useEffect, useState } from "react";
import { theme } from "./colors";
import AsyncStorage from "@react-native-async-storage/async-storage";
const STORAGE_KEY = "@toDos";
export default function App() {
const [working, setWorking] = useState(true);
const [text, setText] = useState("");
const [toDos, setToDos] = useState({});
useEffect(() => {
loadToDos();
}, []);
const travel = () => setWorking(false);
const work = () => setWorking(true);
const onChangeText = (payload) => setText(payload);
const saveToDos = async (toSave) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
} catch (e) {
console.log("saving error");
}
};
const loadToDos = async () => {
try {
const s = await AsyncStorage.getItem(STORAGE_KEY);
// console.log("storage : ", s);
setToDos(JSON.parse(s));
} catch (e) {
console.log("AsyncStorage.getItem error");
}
};
const addToDo = async () => {
if (text === "") {
return;
}
// alert(text);
const newToDos = Object.assign({}, toDos, {
[Date.now()]: { text, working },
});
setToDos(newToDos);
await saveToDos(newToDos);
setText("");
};
console.log(toDos);
return (
<View style={styles.container}>
<StatusBar style="auto" />
<View style={styles.header}>
<TouchableOpacity onPress={work}>
<Text
style={{ ...styles.btnText, color: working ? "white" : theme.gray }}
>
Work
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={travel}>
<Text
style={{
...styles.btnText,
color: !working ? "white" : theme.gray,
}}
>
Travel
</Text>
</TouchableOpacity>
</View>
<View style={styles.mainArea}>
<TextInput
onSubmitEditing={addToDo}
onChangeText={onChangeText}
returnKeyType="done"
value={text}
placeholder={working ? "Add a To Do" : "Where do you want to go?"}
style={styles.input}
/>
<ScrollView style={styles.list}>
{Object.keys(toDos).map((key) =>
toDos[key].working === working ? (
<View style={styles.toDo} key={key}>
<Text style={styles.toDoText}>{toDos[key].text}</Text>
</View>
) : null
)}
</ScrollView>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.background,
paddingHorizontal: 20,
},
header: {
justifyContent: "space-between",
flexDirection: "row",
marginTop: 100,
},
mainArea: {
flex: 1,
},
btnText: {
fontSize: 38,
fontWeight: "600",
},
input: {
backgroundColor: "white",
paddingVertical: 15,
paddingHorizontal: 20,
borderRadius: 30,
marginVertical: 20,
fontSize: 18,
},
list: {},
toDo: {
backgroundColor: theme.toDoBg,
marginBottom: 10,
paddingVertical: 20,
paddingHorizontal: 20,
borderRadius: 15,
},
toDoText: {
color: "#fff",
fontSize: 16,
fontWeight: "500",
},
});
# 3.6 Delete
toDo를 지우는 버튼을 만든고 지우는 로직을 작성합니다.
코드
const deleteToDo = (key) => {
Alert.alert("Delete To Do", "Are you sure", [
{
text: "Cancel",
},
{
text: "I'm Sure",
style: "destructive",
onPress: async () => {
const newToDos = { ...toDos };
delete newToDos[key];
setToDos(newToDos);
await saveToDos(newToDos);
},
},
]);
};
## delete 연산자
delete 연산자는 객체의 속성을 제거합니다.
제거한 객체의 참조를 어디에서도 사용하지 않는다면 나중에 자원을 회수합니다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/delete
## Alert
지정된 제목과 메시지로 경고 대화 상자를 실행합니다.
https://reactnative.dev/docs/alert
## expo 아이콘
import { Fontisto } from "@expo/vector-icons";
// ...
<Fontisto name="trash" size={18} color={theme.grey} />