{"id":18830956,"url":"https://github.com/ssi02014/react-native-chat","last_synced_at":"2026-04-14T03:32:24.425Z","repository":{"id":52796341,"uuid":"355939817","full_name":"ssi02014/React-Native-Chat","owner":"ssi02014","description":"💬  React Native로 만든 Chatting App","archived":false,"fork":false,"pushed_at":"2021-04-19T07:23:02.000Z","size":1445,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-22T19:31:49.362Z","etag":null,"topics":["firebase","react","react-native"],"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/ssi02014.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}},"created_at":"2021-04-08T14:28:43.000Z","updated_at":"2021-10-21T12:01:31.000Z","dependencies_parsed_at":"2022-08-22T22:50:42.104Z","dependency_job_id":null,"html_url":"https://github.com/ssi02014/React-Native-Chat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ssi02014/React-Native-Chat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssi02014%2FReact-Native-Chat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssi02014%2FReact-Native-Chat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssi02014%2FReact-Native-Chat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssi02014%2FReact-Native-Chat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssi02014","download_url":"https://codeload.github.com/ssi02014/React-Native-Chat/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssi02014%2FReact-Native-Chat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31781292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["firebase","react","react-native"],"created_at":"2024-11-08T01:51:24.892Z","updated_at":"2026-04-14T03:32:24.410Z","avatar_url":"https://github.com/ssi02014.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 💻 React-Native-Chat\n### React-Native-Chat 저장소\n\n\u003cbr /\u003e\n\n## 🎥 App View\n\u003c!-- \u003cp align='center'\u003e\n    \u003cimg src='' width=\"400\" height=\"730\"\u003e\n\u003c/p\u003e --\u003e\n\n\u003cbr /\u003e\n\n## 📚 기술 스택 및 주요 라이브러리\n1. React-Native\n2. Styled-Components: Styling\n3. Google Material Design: Icon\n4. React-Navigation(Stack, Tab)\n5. Context API: 상태 관리\n6. Firebase: 서비스에 필요한 서버와 데이터베이스를 직접 구축하지 않고 개발이 가능한 개발 플랫폼\n6. expo-image-picker: 기기의 사진이나 영상을 가져올 수 있도록 시스템 UI에 접근할 수 있는 기능을 제공\n7. moment: 시간을 다양한 형태로 변경하는 등 시간과 관련된 많은 기능을 제공\n8. react-native-keyboard-aware-scroll-view: 키보드가 화면을 가리며서 생기는 불편한 점을 해결할 수 있는 기능 제공\n9. react-native-gifted-chat: 메시지를 주고받는 채팅 화면을 쉽게 구현할 수 있도록 돕는 라이브러리\n\n\u003cbr /\u003e\n\n## 📂 App File Structure\n![1](https://user-images.githubusercontent.com/64779472/114047660-cf11e300-98c4-11eb-9c6a-8a92a932eec3.PNG)\n\n- components: 컴포넌트 파일 관리\n- contexts: Context API 파일 관리\n- navigations: 내비게이션 파일 관리\n- screens: 화면 파일 관리\n- utils: 프로젝트에서 이용할 기타 기능 관리\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 Firebase\n🔖 **https://console.firebase.google.com/**\n- Firebase는 인증(Authentication), 데이터베이스(Database) 등의 다양한 기능을 제공하는 개발 플랫폼이다.\n- Firebase가 제공하는 기능을 이용하면 대부분의 서비스에서 필요한 서버와 데이터베이스를 직접 구축하지 않아도 개발이 가능하다.\n\n```javascript\n    //Firebase Setting\n    1. 프로젝트 설정 \u003e 일반 \u003e 내 앱에서 '웹'을 선택하고 앱을 추가\n    \n    2. 프로젝트 설정 \u003e 일반 \u003e 내 앱에서 'Firebase SDK snippet'에서 Firebase 설정값을 확인한다.\n\n    3. 프로젝트 루트 디렉터리에 firebase.json 파일을 생성 후 2번에서 확인한 코드를 넣는다.\n         - firebase.json은 중요한 파일이기 때문에 .gitignore에 추가한다.\n\n    //firebase.json\n    {\n        \"apiKey\": \"...\",\n        \"authDomain\": \"...\",\n        \"projectId\": \"...\",\n        \"storageBucket\": \"...\",\n        \"messagingSenderId\": \"...\",\n        \"appId\": \"...\",\n        \"measurementId\": \"...\"\n    }\n\n    4. 인증, 데이터베이스, 스토리지 설정한다.\n\n    5. expo install firebase 를 통해 라이브러리를 설치한다.\n\n    6. firebase.js 파일을 생성한다.\n\n    //src/utils/firebase.js\n    import * as firebase from \"firebase\";\n    import config from \"../../firebase.json\";\n\n    const app = firebase.initializeApp(config);\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 앱 아이콘과 로딩 화면\n- 프로젝트에서 사용할 이미지와 폰트를 미리 불러와서 사용할 수 있도록 cacheImages, cacheFonts 함수를 작성하고 이를 _loadAssets 함수를 구성했다.\n- 이미지나 폰트를 미리 불러오면 애플리케이션을 사용하는 환경에 따라 이미지나 폰트가 느리게 적용되는 문제를 개선할 수 있다.\n- 애플리케션은 미리 불러와야 하는 항목들을 모두 불러오고 화면이 렌더링 되도록 AppLoading 컴포넌트를 사용한다.\n\n```javascript\n    const cacheImages = (images) =\u003e {\n        return images.map((image) =\u003e {\n            if (typeof image === \"string\") {\n                return Image.prefetch(image);\n            } else {\n                return Asset.fromModule(image).downloadAsync();\n            }\n          });\n        };\n\n    const cacheFonts = (fonts) =\u003e {\n        return fonts.map((font) =\u003e Font.loadAsync(font));\n    };\n\n    const App = () =\u003e {\n        (...)\n\n    const _loadAssets = async () =\u003e {\n        const imageAssets = cacheImages([\n            require(\"../assets/splash.png\"),\n        ]);\n\n        const fontAssets = cacheFonts([]);\n\n        await Promise.all([...imageAssets, ...fontAssets]);\n    };\n\n    return isReady ? (\n        (...)\n    ) : (\n        \u003cAppLoading\n            startAsync={_loadAssets}\n            onFinish={() =\u003e setIsReady(true)}\n            onError={console.error}\n        /\u003e\n      );\n    };\n```\n\n\u003cbr /\u003e\n\n\n## 👨🏻‍💻 로고 적용하기\n- 이번 애플리케이션의 로고를 Firebase 스토리지에 업로드하고 로그인 화면에서 사용하도록 만들었습니다.\n- 스토리지에 파일을 업로드하고 파일 정보에서 이름을 클릭하면 해당 파일의 url을 얻을 수 있습니다.\n\n```javascript\n    //1. src/utils/images.js 생성\n\n    const prefix =\n        \"https://firebasestorage.googleapis.com/v0/b/react-native-chat-65246.appspot.com/o\";\n\n    export const images = {\n        logo: `${prefix}/logo.png?alt=media`,\n    };\n\n    //2. src/App.js (_loadAssets 메서드 수정)\n\n    const _loadAssets = async () =\u003e {\n        const imageAssets = cacheImages([\n            require(\"../assets/splash.png\"),\n            ...Object.values(images),\n        ]);\n\n        const fontAssets = cacheFonts([]);\n\n        await Promise.all([...imageAssets, ...fontAssets]);\n    };\n\n    //3. Firebase 스토리지 Rules 수정\n\n    rules_version = '2';\n    service firebase.storage {\n      match /b/{bucket}/o {\n        match /logo.png {\n          allow read;\n        }\n      }\n    }\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 useRef, forwardRef\n- useRef를 이용하여 passwordRef를 만들고 비밀번호를 입력하는 Input 컴포넌트의 ref로 지정했습니다. \n- 이메일을 입력하는 Input 컴포넌트의 onSubmitEditing 함수를 passwordRef 를 이용해서 비밀번호를 입력하는 Input 컴포넌트로 포커스가 이동되도록 작성합니다.\n- ref는 key처럼 리액트에서 자식 컴포넌트의 props로 전달되지 않습니다. 이때, forwardRef 함수를 이용하면 ref를 전달받을 수 있습니다.\n\n```javascript\nconst Input = forwardRef(\n  (\n    {\n      (...)\n    },\n    ref\n  ) =\u003e {\n      \n    return (\n      \u003cContainer\u003e\n        (...)\n        \u003cStyledTextInput\n          ref={ref}\n          isFocused={isFocused}\n          value={value}\n          onChangeText={onChangeText}\n          onSubmitEditing={onSubmitEditing}\n          onFocus={() =\u003e setIsFocused(true)}\n          onBlur={() =\u003e {\n            setIsFocused(false);\n            onBlur();\n          }} //input에 포커스가 풀릴때 호출되는 콜백\n          placeholder={placeholder}\n          secureTextEntry={isPassword} //문자를 감추는 기능\n          returnKeyType={returnKeyType} //리턴 키를 레이블로 설정\n          maxLength={maxLength} //입력 할 수있는 최대 문자 수를 제한\n          autoCapitalize=\"none\" //자동 대문자 변환\n          autoCorrect={false} //자동 수정\n          textContentType=\"none\" //iOS\n          underlineColorAndroid=\"transparent\" //Android TextInput 밑줄 의 색상\n        /\u003e\n      \u003c/Container\u003e\n    );\n  }\n);\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 키보드 감추기\n- TextInput에 입력 도중 다른 곳을 터치하면 키보드가 사라지는데, 이는 사용자 편의를 위한 일반적인 애플리케이션의 동장 방식입니다.\n- 리액트 네이티브에서 **TouchableWithoutFeedback** 컴포넌트와 **Keyboard API**를 사용해서 위 동장 방식을 구현할 수 있습니다.\n- 위에 두 컴포넌트는 대신, 위치에 따라 키보드가 Input 컴포넌트를 가리는 문제를 해결하지는 못합니다.\n- **react-native-keyboard-aware-scroll-view** 라이브러리를 이용하면 위 문제를 해결할 수 있습니다. 뿐만 아니라 focus가 있는 TextInput 컴포넌트의 위치로 자동 스크롤되는 기능 등 Input 컴포넌트에 필요한 기능들을 제공합니다.\n\n```javascript\n  //import \n  import { KeyboardAwareScrollView } from \"react-native-keyboard-aware-scroll-view\";\n\n  \u003cKeyboardAwareScrollView\n    contentContainerStyle={{ flex: 1 }}\n    extraScrollHeight={20} //스크롤되는 위치를 조정할 때 사용\n  \u003e\n    (...)\n  \u003c/KeyboardAwareScrollView\u003e\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 이메일 유효성 검사\n```javascript\n  //올바른 이메일 형식 검사\n  export const validateEmail = (email) =\u003e {\n    const regex = /^[0-9?A-z0-9?]+(\\.)?[0-9?A-z0-9?]+@[0-9?A-z]+\\.[A-z]{2}.?[A-z]{0,3}$/;\n\n    return regex.test(email);\n  };\n\n  //공백 제거\n  export const removeWhitespace = (text) =\u003e {\n    const regex = /\\s/g;\n    return text.replace(regex, \"\");\n  };\n```\n\u003cbr /\u003e\n\n## 👨🏻‍💻 Button 컴포넌트\n- **TouchableOpacity**는 터치 이벤트(onPress)를 사용할 수 있는 View\n\n```javascript\n  const Container = styled.TouchableOpacity`\n    (...)\n  `;\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 노치 디자인\n- react-native-safe-area-context 라이브러리가 제공하는 useSafeAreaInsets Hook 함수를 이용하면 노치디자인을 해결할 수 있다.\n- useSafeAreaInsets의 장점은 iOS뿐만아니라 안드로이드에서도 적용 가능한 padding 값을 전달한다.\n\n```javascript\n  //import \n  import { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\n  //padding top과 bottom의 값을 useSafeAreaInsets 함수가 알려주는 값만큼 설정한다.\n  const Container = styled.View`\n    (...)\n    padding: 0 20px;\n    padding-top: ${({ insets: { top } }) =\u003e top}px;\n    padding-bottom: ${({ insets: { bottom } }) =\u003e bottom}px;\n  `;\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 권한 요청, 사진의 정보 가져오기\n### 🏃권한 요청(iOS)\n- expo-image-picker 라이브러리를 통해서 기기의 사진첩에 접근해서 선택된 사진의 정보를 가져올 수 있다.\n- iOS에서는 사진첩에 접근하기 위해 사용자에게 권한을 요청하는 과정이 필요하므로, 권한을 요청하는 부분을 추가해야 한다. 안드로드에서는 특별한 설정 없이 사진에 접근할 수 있다.\n\n```javascript\n  //install\n  expo install expo-image-picker\n\n  //import \n  import * as ImagePicker from \"expo-image-picker\";\n  import * as Permissions from \"expo-permissions\";\n\n  //iOS 권한 요청\n  useEffect(() =\u003e {\n    async () =\u003e {\n      try {\n        if (Platform.OS === \"ios\") {\n          const { status } = await Permissions.askAsync(\n            Permissions.CAMERA_ROLL\n          );\n          if (status !== \"granted\") {\n            Alert.alert(\n              \"Photo Permission\",\n              \"Please turn on the camera roll permissions\"\n            );\n          }\n        }\n      } catch (e) {\n        Alert.alert(\"Photo Permission Error\", e.message);\n      }\n    };\n  }, []);\n```\n\u003cbr /\u003e\n\n### 🏃사진 입력받기\n- 사진 변경 버튼을 클릭하면 호출되는 함수에서 기기의 사진에 접근하기 위해 호출되는 라이브러리 함수는 다음과 같은 값들을 포함한 객체를 파라미터로 전달받는다.\n  1. mediaTypes: 조회하는 자료의 타입\n  2. allowsEditing: 이미지 선택 후 편집 단계 진행 여부\n  3. aspect: 안드로이드 전용 옵션으로 이미지 편집시 사각형의 비율([x, y])\n  4. quality: 0 ~ 1 사이의 값을 받으며 압축 품질을 의미 (1: 최대 품질)\n\n```javascript\n  const _handelEditButton = async () =\u003e {\n      try {\n        const result = await ImagePicker.launchCameraAsync({\n          mediaTypes: ImagePicker.MediaTypeOptions.images,\n          allowsEditing: true,\n          aspect: [1, 1],\n          quality: 1,\n        });\n        if (!result.cancelled) {\n          onChangeImage(result.uri);\n        }\n      } catch (e) {\n        Alert.alert(\"Photo Error\", e.message);\n      }\n    };\n```\n\u003cbr /\u003e\n\n- 기기의 사진에 접근하는 함수는 결과를 반환하는데, cancelled 값을 통해 선택 여부를 확인할 수 있다. 만약 사용자가 사진을 선택했다면 반환된 결과의 uri를 통해 선택된 사진의 주소를 알 수 있다.\n\n```json\n  //상단에 result의 반환 값\n  {\n    \"cancelled\": true,\n  }\n\n  {\n    \"cancelled\": false,\n    \"height\": 000,\n    \"type\": \"image\",\n    \"uri\": \"file:../...jpg\",\n    \"width\": 000,\n  }\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 로그인, 회원가입 기능 구현\n### 🏃 로그인\n- 파이어베이스를 이용한 이메일과 비밀번호를 이용해 인증받는 함수는  **signInWithEmailAndPassword** 입니다.\n\n```javascript\n  //utils/firebase.js\n  export const login = async ({ email, password }) =\u003e {\n    const { user } = await Auth.signInWithEmailAndPassword(email, password);\n    return user;\n  };\n```\n\n\u003cbr /\u003e\n\n```javascript\n  //screens/Login.js\n  import { login } from \"../utils/firebase\";\n\n  const _handleLoginButtonPress = async () =\u003e {\n    try {\n      const user = await login({ email, password });\n      Alert.alert(\"Login Success\", user.email);\n    } catch (e) {\n      Alert.alert(\"Login Error\", e.message);\n    }\n  };\n```\n\u003cbr /\u003e\n\n### 🏃 회원가입\n- 파이어베이스를 이용한 이메일과 비밀번호를 이용해 사용자를 생성하는 함수는 **createUserWithEmailAndPassword** 입니다.\n\n```javascript\n//utils/firebase.js\n\n  export const signup = async ({ email, password, name, photoUrl }) =\u003e {\n    const { user } = await Auth.createUserWithEmailAndPassword(email, password);\n    return user;\n  };\n```\n\u003cbr /\u003e\n\n```javascript\n  //screens/Signup.js\n  import { signup } from \"../utils/firebase\";\n\n  const _handleSignupButtonPress = async () =\u003e {\n    try {\n      const user = await signup({ email, password, name, photoUrl });\n      console.log(user);\n      Alert.alert(\"Signup Success\", user.email);\n    } catch (e) {\n      Alert.alert(\"Signup Error\", e.message);\n    } \n  };\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 Spinner(with ContextAPI)\n### 🏃 Spinner Component\n- Spinner 컴포넌트는 로그인 혹은 회원가입이 진행되는 동안 데이터를 수정하거나 버튼을 추가로 클릭하는 일이 발생하지 않도록 방지하는 기능을 한다.\n- Spinner 컴포넌트는 리액트 네이티브에서 제공하는 **AcitivityIndicator** 컴포넌트를 이용해서 쉽게 만들 수 있다.\n- Spinner 컴포넌트를 AuthStack 내비게이션의 하위 컴포넌트로 사용하면 내비게이션을 포함한 화면 전체를 차지할 수 없습니다. 내비게이션을 포함한 화면 전체를 감싸기 위해서는 navigations 폴더의 index.js에서 AuthStack 내비게이션과 같은 위치에 Spinner 컴포넌트를 사용해야 됩니다.\n\n```javascript\n  import React, { useContext } from 'react';\n  import { ActivityIndicator } from 'react-native';\n  import styled, { ThemeContext } from 'styled-components/native';\n\n  const Container = styled.View`\n    (...)\n  `;\n\n  const Spinner = () =\u003e {\n      const theme = useContext(ThemeContext);\n      return (\n          \u003cContainer\u003e\n              \u003cActivityIndicator size={'large'} color={theme.spinnerIndicator} /\u003e\n          \u003c/Container\u003e\n      )\n  };\n\n  export default Spinner;\n```\n\u003cbr /\u003e\n\n- Spinner 컴포넌트를 AuthStack 내비게이션의 하위 컴포넌트로 사용하면 내비게이션을 포함한 화면 전체를 차지할 수 없습니다. 내비게이션을 포함한 화면 전체를 감싸기 위해서는 navigations 폴더의 index.js에서 AuthStack 내비게이션과 같은 위치에 Spinner 컴포넌트를 사용해야 됩니다.\n\n```javascript\n  (...)\n  const Navigation = () =\u003e {\n    const { inProgress } = useContext(ProgressContext);\n\n    return (\n      \u003cNavigationContainer\u003e\n        \u003cAuthStack /\u003e\n        {inProgress \u0026\u0026 \u003cSpinner /\u003e}\n      \u003c/NavigationContainer\u003e\n    );\n  };\n  (...)\n```\n\u003cbr /\u003e\n\n### 🏃 Context API\n- createContext 함수를 이용해 Context를 생성하고, Provider 컴포넌트의 value에 Spinner 컴포넌트의 렌더링 상태를 관리할 inPrgress 상태 변수와 상태를 변경할 수 있는 함수를 전달합니다.\n\n```javascript\n  //contexts/Progress.js\n\n  import React, { useState, createContext } from 'react';\n\n  //Context 생성\n  const ProgressContext = createContext({\n    inProgress: false,\n    spinner: () =\u003e {},\n  });\n\n  //ProgressProvider\n  const ProgressProvider = ({ children }) =\u003e {\n    const [inProgress, setInProgress] = useState(false);\n\n    const spinner = {\n      start: () =\u003e setInProgress(true),\n      stop: () =\u003e setInProgress(false),\n    };\n    const value = { inProgress, spinner };\n\n    return (\n      \u003cProgressContext.Provider value={value}\u003e\n          {children}\n      \u003c/ProgressContext.Provider\u003e\n    );\n  };\n\n  export { ProgressContext, ProgressProvider };\n```\n\u003cbr /\u003e\n\n```javascript\n  //contexts/index.js\n  import {ProgressContext, ProgressProvider } from './Progress';\n  export { ProgressContext, ProgressProvider }; \n```\n\u003cbr /\u003e\n\n```javascript\n  //src/App.js\n  \u003cThemeProvider theme={theme}\u003e\n    \u003cProgressProvider\u003e\n      \u003cStatusBar barStyle=\"light-content\" /\u003e\n      \u003cNavigation /\u003e\n    \u003c/ProgressProvider\u003e\n  \u003c/ThemeProvider\u003e\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 Stack 내비게이션 속 Tab 내비게이션의 헤더 변경\n- MainStack 내비게이션에서 MainTab 내비게이션이 화면으로 사용되는 Screen 컴포넌트의 name은 \"Main\"으로 설정되어 있다. 헤더의 타이틀과 관련해 특별히 설정하지 않으면 Screen 컴포넌트의 name에 설정된 값이 헤더의 타이틀로 되기 때문에, 프로필 화면과 채널 목록 모두 'Main'으로 타이틀이 나타난다.\n```javascript\n  \u003cStack.Navigator\n    initialRouteName=\"Main\"\n    (...)\n  \u003e   \n    \u003cStack.Screen name=\"Main\" component={MainTab} /\u003e\n    (...)\n  \u003c/Stack.Navigator\u003e\n```\n\u003cbr /\u003e\n\n- MainTab 내비게이션은 MainStack 내비게이션의 화면으로 사용되었기 때문에 다른 화면들과 마찬가지로 props를 통해 navigation과 route를 전달 받는다.\n- route에 포함된 state의 값은 다음과 같다\n  1. index: 현재 렌더링 되는 화면의 인덱스\n  2. routeNames: 화면으로 사용되는 Navigator 컴포넌트에서 Screen 컴포넌트들의 name 속성을 배열로 갖는다.\n  3. type: 현재 화면으로 사용되는 Navigator 컴포넌트의 타입이며, MainTab 내비게이션은 탭 내비게이션이기 때문에 'tab' 값을 갖는다.\n\n```json\n  //route의 state\n  {\n    \"index\": 0,\n    \"routeNames\": [\n      \"Channel List\",\n      \"Profile\",\n    ],\n    \"type\": \"tab\",\n    ...\n  }\n```\n\u003cbr /\u003e\n\n```javascript\n  //MainTab\n  useEffect(() =\u003e {\n    const titles = route.state?.routeNames || ['Channels'];\n    const index = route.state?.index || 0;\n    navigation.setOptions({ headerTitle: titles[index ]});\n  }, [route]);\n```\n\n\u003cbr /\u003e\n\n- 하지만 위에 방식대로 하면 route의 state에 직접 접근해서 발생하는 경고메시지가 뜬다. 이걸 해결하려면 **getFocusedRouteNameFromRoute** 메서드를 사용하면 된다.\n- 🔖 관련 이슈: https://github.com/Alchemist85K/my-first-react-native/discussions/26\n\n```javascript\n  useEffect(() =\u003e {\n      const screenName = getFocusedRouteNameFromRoute(route) || 'Channels';\n\n      navigation.setOptions({ \n        headerTitle: screenName,\n      });\n  }, [route]);\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 Setting a timer for a long period of time, ... 오류\n- 🔖 관련 이슈: https://github.com/Alchemist85K/my-first-react-native/discussions/28\n- /node_modules/react-native/Libraries/Core/Timers/JSTimers.js\n- MAX_TIMER_DURATION_MS 라는 변수 값을 60 * 1000 에서 10000 * 1000으로 변경\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 데이터베이스\n- 파이어베이스에서 제공하는 파이어스토어는 NoSQL 문서 중심의 데이터베이스이다.\n- SQL 데이터베이스와 달리 테이블이나 행이 없고 컬렉션, 문서, 필드로 구성된다.\n  1. 컬렉션은 문서의 컨테이너 역할을 하며, 모든 문서는 항상 컬렉션에 저장된다.\n  2. 문서는 파이어스토어의 저장 단위로 값이 있는 필드를 갖는다. 문서의 가장 큰 특징은 컬렉션을 필드로 가질 수 있다.\n- 파이어스토어는 일반적인 데이터베이스와 달리 데이터베이스의 내용이 수정되면 실시간으로 변경된 내용을 알 수 있다.\n- 컬렉션과 문서는 항상 유일한 ID를 갖고 있어야 한다는 규칙이 있다.\n\n![1](https://user-images.githubusercontent.com/64779472/114672004-a44de180-9d3f-11eb-9646-eaa072f40f2c.PNG)\n\n```\n  //파이어스토어 보안 규칙 수정\n  rules_version = '2';\n    service cloud.firestore {\n      match /databases/{database}/documents {\n        match /channels/{channel} {\n          allow read, write: if request.auth.uid != null;\n        }\n      }\n    }\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 FlatList\n- 지금까지 많은 양의 데이터를 렌더링할 때 ScrollView 컴포넌트를 이용해 화면이 넘어가도록 스크롤이 생성되어 확인할 수 있도록 만들었습니다.\n- FlatList컴포넌트는 ScrollView 컴포넌트와 같은 역할을 하는데, ScrollView 컴포넌트는 렌더링해야 하는 모든 데이터를 한번에 렌더링합니다. 즉, 데이터의 양을 알고 있을 때 사용하는 것이 좋고, 데이터가 매우 많으면 렌더링 속도가 느려지고 메모리 사용량이 증가하는 등 성능이 저하됩니다.\n- 그에반해, FlatList는 화면에 적절한 양의 데이터만 렌더링하고 스크롤의 이동에 맞춰 필요한 부분을 추가적으로 렌더링하는 특징이 있습니다.\n- FlatList에는 기본적으로 3가지 속성이 있습니다.\n  1. data: 처음 렌더링할 항목의 데이터를 배열로 전달한다.\n  2. renderItem: 전달된 배열의 항목을 이용해 렌더링하는 함수를 작성해야 한다.\n  3. keyExtractor: key를 추가하기 위해 고유한 값을 반환하는 함수를 전달해야 한다.\n  \n\u003cbr /\u003e\n\n```js\n  \u003cFlatList\n    keyExtractor={item =\u003e item['id'].toString()}\n    data={channels}\n    renderItem={({ item }) =\u003e {\n        return (\n            \u003cItem item={item} onPress={_handleItemPress} /\u003e\n        )\n    }}\n    windowSize={3}\n  /\u003e\n```\n\n\u003cbr /\u003e\n\n### 🏃 windowSize\n- FlatList에서 렌더링 되는 데이터의 양을 조절하고 싶다면 windowSize 속성을 추가해서 값을 원하는 값으로 설정하면 된다. \n- windowSize의 값을 작은 값으로 변경하면 렌더링되는 데이터가 줄어들어 메모리의 소비를 줄이고 성능을 향상 시킬 수 있지만, 빠르게 스크롤하는 상황에서 미리 렌더링되지 않은 부분은 순간적으로 빈 내용이 나타날 수 있다는 단점이 있다.\n\n\u003cbr /\u003e\n\n```js\n  \u003cFlatList\n    (...)\n    windowSize={3}\n  /\u003e\n```\n\n\u003cbr /\u003e\n\n### 🏃 React.Memo\n- React.Memo는 useMemo Hook 함수와 비슷하지만, 불필요한 함수의 재연산을 방지하는 useMemo와 달리 컴포넌트의 리렌더링을 방지한다는 차이가 있다.\n- React.Memo는 컴포넌트를 감싸는 것으로 간단히 적용할 수 있다.\n- React.Memo를 사용하면 Item 컴포넌트는 props가 변경될 때까지 리렌더링되지 않는다.\n\n\u003cbr /\u003e\n\n```js\nconst Item = React.memo(({ item: { id, title, description, createdAt }, onPress }) =\u003e {\n  const theme = useContext(ThemeContext);\n  console.log(`Item ${id}`);\n\n  return (\n    \u003cItemContainer onPress={() =\u003e onPress({ id, title })}\u003e\n        \u003cItemTextContainer\u003e\n            \u003cItemTitle\u003e{title}\u003c/ItemTitle\u003e\n            \u003cItemDescription\u003e{description}\u003c/ItemDescription\u003e\n        \u003c/ItemTextContainer\u003e\n        \u003cItemTime\u003e{createdAt}\u003c/ItemTime\u003e\n        \u003cMaterialIcons \n            name=\"keyboard-arrow-right\"\n            size={24}\n            color={theme.listIcon}\n        /\u003e\n    \u003c/ItemContainer\u003e\n  );\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 채널 데이터 수신\n- firebase의 Cloud Firestore에서 실시간 데이터를 받아오기 위해서는 onSnapshop 메서드를 이용하여 데이터를 수신할 수 있습니다.\n- **onSnapshop** 메서드는 수신 대기 상태로 있다가 데이터베이스에 문서가 추가되거나 수정될 때마다 지정된 함수가 호출됩니다.\n- 이때, 오름차순, 내림차순은 **orderBy** 메서드를 이용해서 할 수 있습니다.\n\n\u003cbr /\u003e\n\n```js\n  useEffect(() =\u003e {\n    const unsubscribe = DB.collection('channels')\n      .orderBy('createdAt', 'desc')\n      .onSnapshot(snapshot =\u003e {\n        const list = [];\n\n        snapshot.forEach(doc =\u003e {\n          list.push(doc.data());\n        });\n        setChannels(list);\n      });\n\n    return () =\u003e unsubscribe();\n  }, []);\n```\n\n\u003cbr /\u003e\n\n## 👨🏻‍💻 react-native-gifted-chat\n- 채팅 화면에서 사용할 수 있는 기능을 다양하게 제공하는 react-native-gifted-chat 라이브러리\n- react-native-gifted-chat 라이브러리의 GiftedChat 컴포넌트는 다양한 설정이 가능하도록 많은 속성을 제공한다.\n  1. 입력된 내용을 설정된 사용자의 정보 및 자동으로 생성된 ID와 함께 전달 하는 기능\n  2. 전송 버튼을 수정하는 기능\n  3. 스크롤의 위치에 따라 스크롤 위치를 변경하는 버튼 렌더링\n\n\u003cbr /\u003e\n\n```js\n  \u003cContainer\u003e\n    \u003cGiftedChat\n      listViewProps={{\n        style: { backgroundColor: theme.background },\n      }}\n      placeholder=\"Enter a Message\"\n      messages={messages}\n      user={{ _id: uid, name, avatar: photoUrl }}\n      onSend={_handleMessageSend}\n      alwaysShowSend={true}\n      textInputProps={{\n        autoCapitalize: \"none\",\n        autoCorrect: false,\n        textContentType: \"none\",\n        underlineColorAndroid: \"transparent\",\n      }}\n      multiline={false}\n      renderUsernameOnMessage={true}\n      scrollToBottom={true}\n      renderSend={(props) =\u003e \u003cSendButton {...props} /\u003e}\n    /\u003e\n  \u003c/Container\u003e\n```\n\n\u003cbr /\u003e\n\n- user에 다음과 같은 형태로 사용자의 정보를 입력해두면 onSend에 정의한 함수가 호출될 때 입력된 메시지와 사용자의 정보를 포함한 객체를 전달한다.\n\n```\n  User {\n    _id: string | number;\n    name: string;\n    avatar: string | renderFunction;\n  }\n```\n\n\u003cbr /\u003e\n\n```\n  Message {\n    _id: string | number;\n    text: string;\n    createdAt: Date | number;\n    user: User;\n    ...\n  }\n```\n\n\u003cbr /\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssi02014%2Freact-native-chat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssi02014%2Freact-native-chat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssi02014%2Freact-native-chat/lists"}