{"id":26833366,"url":"https://github.com/mr-won/worktravelapp","last_synced_at":"2025-03-30T15:28:48.412Z","repository":{"id":138423066,"uuid":"431743442","full_name":"mr-won/WorkTravelApp","owner":"mr-won","description":"Build Work Travel App by using react-native (리액트 네이티브를 사용해서 할 일과 여행 목록 앱 개발)","archived":false,"fork":false,"pushed_at":"2021-12-01T07:48:06.000Z","size":324,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-18T02:27:16.120Z","etag":null,"topics":["react-native","react-native-project"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mr-won.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-11-25T06:52:39.000Z","updated_at":"2024-07-10T06:09:27.000Z","dependencies_parsed_at":"2023-08-20T14:07:18.909Z","dependency_job_id":null,"html_url":"https://github.com/mr-won/WorkTravelApp","commit_stats":null,"previous_names":["wonttan/worktravelapp","wonchihyeon/worktravelapp","chihyunwon/worktravelapp","mr-won/worktravelapp","chihyeonwon/worktravelapp"],"tags_count":null,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-won%2FWorkTravelApp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-won%2FWorkTravelApp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-won%2FWorkTravelApp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-won%2FWorkTravelApp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mr-won","download_url":"https://codeload.github.com/mr-won/WorkTravelApp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246337811,"owners_count":20761271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react-native","react-native-project"],"created_at":"2025-03-30T15:28:47.704Z","updated_at":"2025-03-30T15:28:48.405Z","avatar_url":"https://github.com/mr-won.png","language":"JavaScript","readme":"# Build Work Travel App by using react-native (리액트 네이티브를 사용해서 할 일과 여행 목록 앱 개발)\n\n## 프로젝트 생성\n\n다음 명령을 터미널에서 실행하여 WorkTravelApp Exop 프로젝트를 생성한다.\n```javascript\ncd Documents\nexpo init WorkTravelApp --npm\n\nblank checked\n```\n\n## Header 영역 생성\n\nHeader 영역에는 Work, Travel 두 개의 버튼을 생성한다.\n\n```javascript\n\u003cView style={styles.header}\u003e\n          \u003cText style={styles.btnText}\u003eWork\u003c/Text\u003e\n          \u003cText style={styles.btnText}\u003eTravel\u003c/Text\u003e\n\u003c/View\u003e\n```\n\n컨테이너영역, 헤더영역, 버튼의 스타일은 다음과 같이 설정한다.\n```javascript\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: '#000',\n    paddingHorizontal: 20,\n  },\n  header: {\n    justifyContent: 'space-between',\n    flexDirection: 'row',\n    marginTop: 100,\n  },\n  btnText: {\n    fontSize: 38,\n    fontWeight: '600',\n    color: 'white',\n  }\n});\n```\n\n테마 배경색을 지정해주는 theme을 export하는 colors.js 파일을 생성한다.\n\n```javascript\nexport const theme = {\n    background:'black',\n    grey: '#3A3D40',\n};\n```\n\nApp.js에서 theme을 import 한다.\n```javascript\nimport { theme } from './colors';\n```\n\ncolors.js 파일의 theme을 사용해 배경색을 바꿔준다.\n```javascript\ncontainer: {\n    flex: 1,\n    backgroundColor: theme.background,\n    paddingHorizontal: 20,\n  },\n```\nWork, Travel Text를 onPress 했을 때 효과를 주는 TouchableOpacity를 import 하고 work, travel을 TouchableOpacity로 감싸준다.\n```javascript\nimport {TouchableOpacity} from 'react-native';\n\n\u003cTouchableOpacity\u003e\n            \u003cText style={styles.btnText}\u003eWork\u003c/Text\u003e\n          \u003c/TouchableOpacity\u003e\n          \u003cTouchableOpacity\u003e\n            \u003cText style={styles.btnText}\u003eTravel\u003c/Text\u003e  \n\u003c/TouchableOpacity\u003e\n```\n\nwork일때와 travel일 때 상태를 저장하는 state 생성한다.\n```javascript\nconst [working, setWorking] = useState(true); // work일때 상태를 저장하는 useState 함수 생성\n  const travel = () =\u003e setWorking(false);\n  const work = () =\u003e setWorking(true);  \n```\n\nTouchableOpacity의 onPress(터치했을때 { }안의 함수를 실행시켜주는 속성)에 work 함수와 travel 함수를 넣어준다.\n```javascript\n\u003cTouchableOpacity onPress={work}\u003e\n            \u003cText style={styles.btnText}\u003eWork\u003c/Text\u003e\n          \u003c/TouchableOpacity\u003e\n          \u003cTouchableOpacity onPress={travel}\u003e\n            \u003cText style={styles.btnText}\u003eTravel\u003c/Text\u003e  \n\u003c/TouchableOpacity\u003e\n```\n\nwork와 Travel 둘 중에 선택하는 Text에 따라 work와 travel함수를 실행시켜서 바뀐 working 상태(true or false)에 따라서 폰트 색깔을 변경한다.\n```javascript\n\u003cTouchableOpacity onPress={work}\u003e\n            \u003cText style={{ ...styles.btnText, color: working ? \"white\" : theme.grey }}\u003eWork\u003c/Text\u003e\n          \u003c/TouchableOpacity\u003e\n          \u003cTouchableOpacity onPress={travel}\u003e\n            \u003cText style={{ ...styles.btnText, color: !working ? \"white\" : theme.grey }}\u003eTravel\u003c/Text\u003e  \n\u003c/TouchableOpacity\u003e\n```\n\n## 텍스트 입력창 생성하기\n\n텍스트 입력을 지원하는 TextInput을 react-native에서 import한다.\n```javascript\nimport {TextInput} from 'react-native';\n```\n\nTextInput의 배경색을 흰색으로 변경한다.\n```javascript\n\u003cTextInput style={styles.input} /\u003e\n\ninput: {\n          backgroundColor: 'white',\n}\n```\n\nTextInput의 속성 중 placeholder를 사용하여 working일때는 'Add a To do' 문구를 working이 아닐 때는 'Where do you want to go' 문구를 넣는다.\n```javascript\n          \u003cTextInput style={styles.input} placeholder={working ? 'Add a To Do' : 'Where do you want to go?'} /\u003e\n```\n\nTextInput의 paddingVertical, paddingHorizontal, borderRadius marginTop, fontSize를 지정하여 꾸며준다.\n```javascript\n input: {\n    backgroundColor: 'white',\n    paddingVertical: 15,\n    paddingHorizontal: 20,\n    borderRadius: 30,\n    marginTop: 20,\n    fontSize: 18,\n  }\n```\nTextInput에 입력한 Text를 저장하는 state 배열을 생성한다.\n```javascript\n const [text, setText] = useState(\"\"); // 입력한 Text를 저장하는 state 함수 생성\n ```\nTextInput에 입력한 Text의 상태를 setText의 payload로 저장하는 onChangeText 함수를 생성한다.\n```javascript\nconst onChangeText = (payload) =\u003e setText(payload); // payload = event\n```\nTextInput의 onChangeText 속성을 사용해 TextInput의 텍스트가 변경되었을 때 onChangeText 함수가 실행되도록 하고 입력한 Text값을 value로 저장한다.\n```javascript\n\u003cTextInput \n            style={styles.input}\n            value={text} \n            placeholder={working ? 'Add a To Do' : 'Where do you want to go?'}\n            onChangeText={onChangeText}  \n/\u003e      \n```\n\nTextInput의 onSubmitEditing 속성을 사용하여 텍스트를 입력하고 확인을 눌렀을 때 addToDo 함수가 발생하도록 설정한다.\n```javascript\n\u003cTextInput onSubmitEditing={addToDo} /\u003e\n```\n\nTextInput의 returnKeyType 속성을 사용하여 키보드의 확인기능을 done으로 수정한다.\n```javascript\n\u003cTextInput returnKeyType=\"done\" /\u003e\n```\n함수 addToDo를 text가 비어있으면 그냥 return 하고 text가 들어있으면 저장하는 함수로 변경한다.\n```javascript\nconst addToDo = () =\u003e {\n    if(text === \"\") {\n      return\n    }\n    // save to do\n    setText(\"\");\n  }\n```\n\n입력하는 텍스트들(toDos)을 저장하고 상태를 관리하는 useState 함수를 Object로 생성한다.\n```javascript\nconst [toDos, setTodos] = useState({});\n```\n\nstate의 수정없이 Object를 결합하는 Object.assign을 사용하여 이전의 todo와 새로운 todo를 결합해서 결과값을 newTodos 변수에 저장한다.\n```javascript\nconst newTodos = Object.assign({}, toDos, {[Date.now()]: {text, work: working}};\n```\n\n세 객체를 결합한 newToDos를 setToDos의 매개변수로 넣는다.\n```javascript\nconst addToDo = () =\u003e {\n    if(text === \"\") {\n      return\n    }\n    const newToDos = Object.assign(\n      {}, // Target Object\n      toDos, // 기존의 toDos\n      {[Date.now()]: {text, work: working}} // 새로운 toDos\n    );\n    setText(\"\");\n    setToDos(newToDos);\n  }          \n```\n\n또 다른 방법으로 ES6에서 지원하는 ...toDos를 사용하여 이전 Object를 가지는 새로운 Object를 만들고 새로운 toDo도 추가하는 방법이 있다.\n```javascript\nconst newToDos = { ...toDos, [Date.now()]: {text, work: working} };\n```\n\n## 입력한 텍스트(toDo)를 화면에 표시하기\n\n스크롤해서 사용자가 많은 todo를 화면에 추가할 수 있도록 하는 ScrollView를 import한다.\n```javascript\nimport { ScrollView } from 'react-native';\n```\nObject의 keys와 map 함수를 사용해서 사용자의 텍스트(toDos)를 화면에 표시한다.\n```javascript\n\u003cScrollView\u003e\n          {Object.keys(toDos).map(key =\u003e \n                    \u003cView style={styles.toDo} key={key}\u003e\n                              \u003cText style={styles.toDoText}\n                                        {toDos[key].text}\n                              \u003c/Text\u003e\n                    \u003c/View\u003e\n          )}\n \u003c/ScrollView\u003e\n ```\n \n toDo와 toDoText 에 다음 스타일을 적용한다.\n ```javascript\n toDo: {\n    backgroundColor: theme.toDoBackground, // color.js 파일에 theme.toDoBackground: \"#5C5C60\"을 추가한다.\n    marginBottom: 10,\n    paddingVertical: 20,\n    paddingHorizontal: 20,\n    borderRadius: 15,\n  },\n  toDoText: {\n    color: 'white',\n    fontSize: 16,\n    fontWeight:\"500\",\n  }\n```\n\n## Work와 Travel의 toDo 나누기\n\nnewToDo의 work:working을 working으로 수정한다.\n```javascript\nconst newToDos = { ...toDos, [Date.now()]: {text, working} };\n```\n\n삼항연산자를 사용해서 현재 toDo의 working이 지금 존재하는 working인지 확인하고 working에 따라 다른 화면을 보여주도록 구현한다.\n```javascript\n\u003cScrollView\u003e\n            {Object.keys(toDos).map(key =\u003e\n              toDos[key].working === working ? (\n              \u003cView style={styles.toDo} key={key}\u003e\n                \u003cText style={styles.toDoText}\u003e\n                  {toDos[key].text}\n                \u003c/Text\u003e\n              \u003c/View\u003e\n              ) : null\n            )}\n\u003c/ScrollView\u003e\n```\n\n## expo가 제공하는 AsyncStorage 모듈을 사용하여 사용자가 입력한 ToDo를 다른페이지로 이동하여도 없어지지 않도록 저장하기\n\n다음 코드를 터미널에서 실행하여 expo AsyncStorage를 설치한다.\n```javascript\nexpo install @react-native-async-storage/async-storage\n```\n그 후 설치한 AsyncStorage를 import 해준다.\n```javascript\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n```\n현재 있는 ToDos를 string으로 바꾸고 setItem을 사용해서 브라우저의 로컬저장소처럼 저장하는 saveToDos 함수를 생성한다.\n```javascript\nconst saveToDos = async (toSave) =\u003e {\n    await AsyncStorage.setItem(\"@toDos\", JSON.stringify(toSave));\n};\n```\n\nsaveToDos 함수에 newToDos를 비동기적으로 전달하기 위해 await를 사용한다.\n```javascript\nawait saveToDos(newToDos);\n```\n\naddToDo 안에 await를 사용한 함수가 있기 때문에 addToDo에 async 키워드를 사용한다.\n```javascript\nconst addToDo = async () =\u003e {\n    if(text === \"\") {\n      return\n    }\n    const newToDos = { ...toDos, [Date.now()]: {text, working} };\n    setText(\"\");\n    await saveToDos(newToDos);\n    setToDos(newToDos);\n  }\n```\n\nStorage Key인 @toDos를 STORAGE_KEY 변수에 담고 이 변수를 setItem의 Storage Key로 사용한다.\n```javascript\nconst STORAGE_KEY = \"@toDos\";\n\nawait AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));\n```\n\nJSON의 parse를 사용하여 string을 Javascript Object로 바꾼 뒤 setToDos로 전달하는 loadToDos 함수 구현한다.\n```javascript\nconst loadToDos = async() =\u003e {\n          const string = await AsyncStorage.getItem(STORAGE_KEY);\n          setToDos(JSON.parse(string);\n};\n```\n\nuseEffect를 사용해서 화면이 렌더링 될 때 loadToDos 함수를 실행시키도록 설정한다.\n```javscript\nuseEffect(() =\u003e {\n    loadToDos();\n  }, []);\n```\n\nloadToDo 함수의 오류와 안정성의 문제로 try catch문을 사용해서 다음과 같이 구현한다.\n```javascript\nasync function loadToDos() {\n    try {\n      const jsonPayload = await AsyncStorage.getItem(STORAGE_KEY);\n      return jsonPayload != null ? setToDos(JSON.parse(jsonPayload)) : null;\n    } catch (error) {\n        console.log(error);\n    }\n}\n```\n\n## ToDo 리스트 항목 삭제 버튼 구현하기\n\n버튼의 쓰레기통 이모지를 import 해준다. \n```javascript\nimport { Fontisto } from \"@expo/vector-icons\";\n```\n쓰레기통 이모지를 구현하고 눌렀을 때 deleteToDo 함수가 실행되도록 구현한다.\n```javascript\n\u003cTouchableOpacity onPress={() =\u003e deleteToDo(key)}\u003e\n                  \u003cFontisto name=\"trash\" size={18} color={theme.grey} /\u003e\n\u003c/TouchableOpacity\u003e\n```\n\n버튼을 감싸고 있는 toDo의 스타일에 다음과 같은 속성들을 추가한다.\n```javascript\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n```\n\ndelete함수를 구현할 때 Alert API를 사용하기 위해서 Alert를 import한다.\n```javascript\nimport { Alert } from 'react-native';\n```\n\ndeleteToDo 함수를 delete 키워드를 사용해서 구현한다.\n```\nconst deleteToDo = (key) =\u003e {\n    Alert.alert(\"Delete To Do\", \"Are you sure?\", [\n      { text: \"Cancel\" },\n      {\n        text: \"I'm Sure\",\n        onPress: () =\u003e {\n          const newToDos = { ...toDos };  // state의 내용으로 새로운 newToDos를 생성\n          delete newToDos[key]; // delete 키워드를 사용해서 newToDos 안의 key를 삭제\n          setToDos(newToDos); // state를 새로운 newToDos로 업데이트\n          saveToDos(newToDos); // AsyncStorage에 저장\n        },\n      },\n    ]);\n  };\n```\n\n## App.js 코드 최종\n\nWork Travel App의 최종 코드는 다음과 같다.\n```javascript\nimport { StatusBar } from 'expo-status-bar';\nimport React, { useEffect, useState } from 'react';\nimport { StyleSheet, Text, View, TouchableOpacity, TextInput, ScrollView, Alert } from 'react-native';\nimport { theme } from './colors';\nimport { Fontisto } from \"@expo/vector-icons\";\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n\nconst STORAGE_KEY = \"@toDos\";\n\nexport default function App() { \n  const [working, setWorking] = useState(true); // work일때 상태를 저장하는 useState 함수 생성\n  const [text, setText] = useState(\"\"); // 입력한 Text의 상태를 저장하는 useState 함수 생성\n  const [toDos, setToDos] = useState({}); // toDos의 상태를 저장하는 useState 함수 생성\n  useEffect(() =\u003e {\n    loadToDos();\n  }, []);\n  const travel = () =\u003e setWorking(false);\n  const work = () =\u003e setWorking(true);  \n  const onChangeText = (payload) =\u003e setText(payload); // payload = event\n  const saveToDos = async (toSave) =\u003e {\n    await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));\n  }; \n  async function loadToDos() {\n    try {\n      const jsonPayload = await AsyncStorage.getItem(STORAGE_KEY);\n      return jsonPayload != null ? setToDos(JSON.parse(jsonPayload)) : null;\n    } catch (error) {\n        console.log(error);\n    }\n}\n  const addToDo = async () =\u003e {\n    if(text === \"\") {\n      return\n    }\n    const newToDos = { ...toDos, [Date.now()]: {text, working} }; \n    setToDos(newToDos);\n    await saveToDos(newToDos);\n    setText(\"\");\n  };\n  const deleteToDo = (key) =\u003e {\n    Alert.alert(\"Delete To Do\", \"Are you sure?\", [\n      { text: \"Cancel\" },\n      {\n        text: \"I'm Sure\",\n        onPress: () =\u003e {\n          const newToDos = { ...toDos };\n          delete newToDos[key];\n          setToDos(newToDos);\n          saveToDos(newToDos);\n        },\n      },\n    ]);\n  };\n  return (\n    \u003cView style={styles.container}\u003e\n      \u003cStatusBar style=\"auto\" /\u003e\n        \u003cView style={styles.header}\u003e\n          \u003cTouchableOpacity onPress={work}\u003e\n            \u003cText style={{ ...styles.btnText, color: working ? \"white\" : theme.grey }}\u003eWork\u003c/Text\u003e\n          \u003c/TouchableOpacity\u003e\n          \u003cTouchableOpacity onPress={travel}\u003e\n            \u003cText style={{ ...styles.btnText, color: !working ? \"white\" : theme.grey }}\u003eTravel\u003c/Text\u003e  \n          \u003c/TouchableOpacity\u003e \n        \u003c/View\u003e\n        \u003cView\u003e\n          \u003cTextInput \n            style={styles.input}\n            value={text} \n            placeholder={working ? 'Add a To Do' : 'Where do you want to go?'}\n            onChangeText={onChangeText} \n            onSubmitEditing={addToDo}  \n            returnKeyType=\"done\"\n          /\u003e\n          \u003cScrollView\u003e\n            {Object.keys(toDos).map(key =\u003e\n              toDos[key].working === working ? (\n              \u003cView style={styles.toDo} key={key}\u003e\n                \u003cText style={styles.toDoText}\u003e\n                  {toDos[key].text}\n                \u003c/Text\u003e\n                \u003cTouchableOpacity onPress={() =\u003e deleteToDo(key)}\u003e\n                  \u003cFontisto name=\"trash\" size={18} color={theme.grey} /\u003e\n                \u003c/TouchableOpacity\u003e\n              \u003c/View\u003e\n              ) : null\n            )}\n          \u003c/ScrollView\u003e\n        \u003c/View\u003e\n    \u003c/View\u003e\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: theme.background,\n    paddingHorizontal: 20,\n  },\n  header: {\n    justifyContent: 'space-between',\n    flexDirection: 'row',\n    marginTop: 100,\n  },\n  btnText: {\n    fontSize: 38,\n    fontWeight: '600',\n  },\n  input: {\n    backgroundColor: 'white',\n    paddingVertical: 15,\n    paddingHorizontal: 20,\n    borderRadius: 30,\n    marginTop: 20,\n    marginVertical: 20,\n  },\n  toDo: {\n    backgroundColor: theme.toDoBackground,\n    marginBottom: 10,\n    paddingVertical: 20,\n    paddingHorizontal: 20,\n    borderRadius: 15,\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n  },\n  toDoText: {\n    color: 'white',\n    fontSize: 16,\n    fontWeight:\"500\",\n  }\n});\n```\n\n## Work Travel 어플리케이션 최종 테스트 화면\n\n#### Work 화면에서 'Work Travel 최종 테스트' 텍스트를 입력했을 때\n![KakaoTalk_20211201_164521473](https://user-images.githubusercontent.com/58906858/144192647-5ce6fdec-ab03-4a37-8135-53918816695c.jpg)\n\n#### 삭제 버튼을 눌렀을 때 취소버튼과 확인버튼이 나오는 화면\n![KakaoTalk_20211201_164521473_01](https://user-images.githubusercontent.com/58906858/144192649-a6252f59-1110-496e-a8ff-3a600112a422.jpg)\n\n#### 삭제버튼의 확인버튼을 눌렀을 때 'Work Travel 최종 테스트' 텍스트가 사라진 후의 화면\n![KakaoTalk_20211201_164521473_02](https://user-images.githubusercontent.com/58906858/144192656-39ee7767-c7b1-4bd7-b0f4-548e940b63b7.jpg)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-won%2Fworktravelapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmr-won%2Fworktravelapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-won%2Fworktravelapp/lists"}