An open API service indexing awesome lists of open source software.

https://github.com/ssi02014/react-native-chat

๐Ÿ’ฌ React Native๋กœ ๋งŒ๋“  Chatting App
https://github.com/ssi02014/react-native-chat

firebase react react-native

Last synced: 3 months ago
JSON representation

๐Ÿ’ฌ React Native๋กœ ๋งŒ๋“  Chatting App

Awesome Lists containing this project

README

          

# ๐Ÿ’ป React-Native-Chat
### React-Native-Chat ์ €์žฅ์†Œ


## ๐ŸŽฅ App View


## ๐Ÿ“š ๊ธฐ์ˆ  ์Šคํƒ ๋ฐ ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
1. React-Native
2. Styled-Components: Styling
3. Google Material Design: Icon
4. React-Navigation(Stack, Tab)
5. Context API: ์ƒํƒœ ๊ด€๋ฆฌ
6. Firebase: ์„œ๋น„์Šค์— ํ•„์š”ํ•œ ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ๊ตฌ์ถ•ํ•˜์ง€ ์•Š๊ณ  ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•œ ๊ฐœ๋ฐœ ํ”Œ๋žซํผ
6. expo-image-picker: ๊ธฐ๊ธฐ์˜ ์‚ฌ์ง„์ด๋‚˜ ์˜์ƒ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์‹œ์Šคํ…œ UI์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณต
7. moment: ์‹œ๊ฐ„์„ ๋‹ค์–‘ํ•œ ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๋“ฑ ์‹œ๊ฐ„๊ณผ ๊ด€๋ จ๋œ ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ œ๊ณต
8. react-native-keyboard-aware-scroll-view: ํ‚ค๋ณด๋“œ๊ฐ€ ํ™”๋ฉด์„ ๊ฐ€๋ฆฌ๋ฉฐ์„œ ์ƒ๊ธฐ๋Š” ๋ถˆํŽธํ•œ ์ ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ ์ œ๊ณต
9. react-native-gifted-chat: ๋ฉ”์‹œ์ง€๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ์ฑ„ํŒ… ํ™”๋ฉด์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ


## ๐Ÿ“‚ App File Structure
![1](https://user-images.githubusercontent.com/64779472/114047660-cf11e300-98c4-11eb-9c6a-8a92a932eec3.PNG)

- components: ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ ๊ด€๋ฆฌ
- contexts: Context API ํŒŒ์ผ ๊ด€๋ฆฌ
- navigations: ๋‚ด๋น„๊ฒŒ์ด์…˜ ํŒŒ์ผ ๊ด€๋ฆฌ
- screens: ํ™”๋ฉด ํŒŒ์ผ ๊ด€๋ฆฌ
- utils: ํ”„๋กœ์ ํŠธ์—์„œ ์ด์šฉํ•  ๊ธฐํƒ€ ๊ธฐ๋Šฅ ๊ด€๋ฆฌ


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Firebase
๐Ÿ”– **https://console.firebase.google.com/**
- Firebase๋Š” ์ธ์ฆ(Authentication), ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(Database) ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๊ฐœ๋ฐœ ํ”Œ๋žซํผ์ด๋‹ค.
- Firebase๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜๋ฉด ๋Œ€๋ถ€๋ถ„์˜ ์„œ๋น„์Šค์—์„œ ํ•„์š”ํ•œ ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ๊ตฌ์ถ•ํ•˜์ง€ ์•Š์•„๋„ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

```javascript
//Firebase Setting
1. ํ”„๋กœ์ ํŠธ ์„ค์ • > ์ผ๋ฐ˜ > ๋‚ด ์•ฑ์—์„œ '์›น'์„ ์„ ํƒํ•˜๊ณ  ์•ฑ์„ ์ถ”๊ฐ€

2. ํ”„๋กœ์ ํŠธ ์„ค์ • > ์ผ๋ฐ˜ > ๋‚ด ์•ฑ์—์„œ 'Firebase SDK snippet'์—์„œ Firebase ์„ค์ •๊ฐ’์„ ํ™•์ธํ•œ๋‹ค.

3. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— firebase.json ํŒŒ์ผ์„ ์ƒ์„ฑ ํ›„ 2๋ฒˆ์—์„œ ํ™•์ธํ•œ ์ฝ”๋“œ๋ฅผ ๋„ฃ๋Š”๋‹ค.
- firebase.json์€ ์ค‘์š”ํ•œ ํŒŒ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— .gitignore์— ์ถ”๊ฐ€ํ•œ๋‹ค.

//firebase.json
{
"apiKey": "...",
"authDomain": "...",
"projectId": "...",
"storageBucket": "...",
"messagingSenderId": "...",
"appId": "...",
"measurementId": "..."
}

4. ์ธ์ฆ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ์Šคํ† ๋ฆฌ์ง€ ์„ค์ •ํ•œ๋‹ค.

5. expo install firebase ๋ฅผ ํ†ตํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

6. firebase.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

//src/utils/firebase.js
import * as firebase from "firebase";
import config from "../../firebase.json";

const app = firebase.initializeApp(config);
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ์•ฑ ์•„์ด์ฝ˜๊ณผ ๋กœ๋”ฉ ํ™”๋ฉด
- ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ด๋ฏธ์ง€์™€ ํฐํŠธ๋ฅผ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก cacheImages, cacheFonts ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ด๋ฅผ _loadAssets ํ•จ์ˆ˜๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค.
- ์ด๋ฏธ์ง€๋‚˜ ํฐํŠธ๋ฅผ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์ด๋ฏธ์ง€๋‚˜ ํฐํŠธ๊ฐ€ ๋А๋ฆฌ๊ฒŒ ์ ์šฉ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
- ์• ํ”Œ๋ฆฌ์ผ€์…˜์€ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•˜๋Š” ํ•ญ๋ชฉ๋“ค์„ ๋ชจ๋‘ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ํ™”๋ฉด์ด ๋ Œ๋”๋ง ๋˜๋„๋ก AppLoading ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

```javascript
const cacheImages = (images) => {
return images.map((image) => {
if (typeof image === "string") {
return Image.prefetch(image);
} else {
return Asset.fromModule(image).downloadAsync();
}
});
};

const cacheFonts = (fonts) => {
return fonts.map((font) => Font.loadAsync(font));
};

const App = () => {
(...)

const _loadAssets = async () => {
const imageAssets = cacheImages([
require("../assets/splash.png"),
]);

const fontAssets = cacheFonts([]);

await Promise.all([...imageAssets, ...fontAssets]);
};

return isReady ? (
(...)
) : (
setIsReady(true)}
onError={console.error}
/>
);
};
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๋กœ๊ณ  ์ ์šฉํ•˜๊ธฐ
- ์ด๋ฒˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋กœ๊ณ ๋ฅผ Firebase ์Šคํ† ๋ฆฌ์ง€์— ์—…๋กœ๋“œํ•˜๊ณ  ๋กœ๊ทธ์ธ ํ™”๋ฉด์—์„œ ์‚ฌ์šฉํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
- ์Šคํ† ๋ฆฌ์ง€์— ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๊ณ  ํŒŒ์ผ ์ •๋ณด์—์„œ ์ด๋ฆ„์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ์˜ url์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

```javascript
//1. src/utils/images.js ์ƒ์„ฑ

const prefix =
"https://firebasestorage.googleapis.com/v0/b/react-native-chat-65246.appspot.com/o";

export const images = {
logo: `${prefix}/logo.png?alt=media`,
};

//2. src/App.js (_loadAssets ๋ฉ”์„œ๋“œ ์ˆ˜์ •)

const _loadAssets = async () => {
const imageAssets = cacheImages([
require("../assets/splash.png"),
...Object.values(images),
]);

const fontAssets = cacheFonts([]);

await Promise.all([...imageAssets, ...fontAssets]);
};

//3. Firebase ์Šคํ† ๋ฆฌ์ง€ Rules ์ˆ˜์ •

rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /logo.png {
allow read;
}
}
}
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป useRef, forwardRef
- useRef๋ฅผ ์ด์šฉํ•˜์—ฌ passwordRef๋ฅผ ๋งŒ๋“ค๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” Input ์ปดํฌ๋„ŒํŠธ์˜ ref๋กœ ์ง€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜๋Š” Input ์ปดํฌ๋„ŒํŠธ์˜ onSubmitEditing ํ•จ์ˆ˜๋ฅผ passwordRef ๋ฅผ ์ด์šฉํ•ด์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜๋Š” Input ์ปดํฌ๋„ŒํŠธ๋กœ ํฌ์ปค์Šค๊ฐ€ ์ด๋™๋˜๋„๋ก ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
- ref๋Š” key์ฒ˜๋Ÿผ ๋ฆฌ์•กํŠธ์—์„œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์˜ props๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋•Œ, forwardRef ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๋ฉด ref๋ฅผ ์ „๋‹ฌ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

```javascript
const Input = forwardRef(
(
{
(...)
},
ref
) => {

return (

(...)
setIsFocused(true)}
onBlur={() => {
setIsFocused(false);
onBlur();
}} //input์— ํฌ์ปค์Šค๊ฐ€ ํ’€๋ฆด๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ
placeholder={placeholder}
secureTextEntry={isPassword} //๋ฌธ์ž๋ฅผ ๊ฐ์ถ”๋Š” ๊ธฐ๋Šฅ
returnKeyType={returnKeyType} //๋ฆฌํ„ด ํ‚ค๋ฅผ ๋ ˆ์ด๋ธ”๋กœ ์„ค์ •
maxLength={maxLength} //์ž…๋ ฅ ํ•  ์ˆ˜์žˆ๋Š” ์ตœ๋Œ€ ๋ฌธ์ž ์ˆ˜๋ฅผ ์ œํ•œ
autoCapitalize="none" //์ž๋™ ๋Œ€๋ฌธ์ž ๋ณ€ํ™˜
autoCorrect={false} //์ž๋™ ์ˆ˜์ •
textContentType="none" //iOS
underlineColorAndroid="transparent" //Android TextInput ๋ฐ‘์ค„ ์˜ ์ƒ‰์ƒ
/>

);
}
);
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ํ‚ค๋ณด๋“œ ๊ฐ์ถ”๊ธฐ
- TextInput์— ์ž…๋ ฅ ๋„์ค‘ ๋‹ค๋ฅธ ๊ณณ์„ ํ„ฐ์น˜ํ•˜๋ฉด ํ‚ค๋ณด๋“œ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š”๋ฐ, ์ด๋Š” ์‚ฌ์šฉ์ž ํŽธ์˜๋ฅผ ์œ„ํ•œ ์ผ๋ฐ˜์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋™์žฅ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
- ๋ฆฌ์•กํŠธ ๋„ค์ดํ‹ฐ๋ธŒ์—์„œ **TouchableWithoutFeedback** ์ปดํฌ๋„ŒํŠธ์™€ **Keyboard API**๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์œ„ ๋™์žฅ ๋ฐฉ์‹์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
- ์œ„์— ๋‘ ์ปดํฌ๋„ŒํŠธ๋Š” ๋Œ€์‹ , ์œ„์น˜์— ๋”ฐ๋ผ ํ‚ค๋ณด๋“œ๊ฐ€ Input ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€๋ฆฌ๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜์ง€๋Š” ๋ชปํ•ฉ๋‹ˆ๋‹ค.
- **react-native-keyboard-aware-scroll-view** ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋ฉด ์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ focus๊ฐ€ ์žˆ๋Š” TextInput ์ปดํฌ๋„ŒํŠธ์˜ ์œ„์น˜๋กœ ์ž๋™ ์Šคํฌ๋กค๋˜๋Š” ๊ธฐ๋Šฅ ๋“ฑ Input ์ปดํฌ๋„ŒํŠธ์— ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

```javascript
//import
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";


(...)

```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ์ด๋ฉ”์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
```javascript
//์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹ ๊ฒ€์‚ฌ
export const validateEmail = (email) => {
const regex = /^[0-9?A-z0-9?]+(\.)?[0-9?A-z0-9?]+@[0-9?A-z]+\.[A-z]{2}.?[A-z]{0,3}$/;

return regex.test(email);
};

//๊ณต๋ฐฑ ์ œ๊ฑฐ
export const removeWhitespace = (text) => {
const regex = /\s/g;
return text.replace(regex, "");
};
```

## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Button ์ปดํฌ๋„ŒํŠธ
- **TouchableOpacity**๋Š” ํ„ฐ์น˜ ์ด๋ฒคํŠธ(onPress)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” View

```javascript
const Container = styled.TouchableOpacity`
(...)
`;
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๋…ธ์น˜ ๋””์ž์ธ
- react-native-safe-area-context ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” useSafeAreaInsets Hook ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๋ฉด ๋…ธ์น˜๋””์ž์ธ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
- useSafeAreaInsets์˜ ์žฅ์ ์€ iOS๋ฟ๋งŒ์•„๋‹ˆ๋ผ ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋„ ์ ์šฉ ๊ฐ€๋Šฅํ•œ padding ๊ฐ’์„ ์ „๋‹ฌํ•œ๋‹ค.

```javascript
//import
import { useSafeAreaInsets } from "react-native-safe-area-context";

//padding top๊ณผ bottom์˜ ๊ฐ’์„ useSafeAreaInsets ํ•จ์ˆ˜๊ฐ€ ์•Œ๋ ค์ฃผ๋Š” ๊ฐ’๋งŒํผ ์„ค์ •ํ•œ๋‹ค.
const Container = styled.View`
(...)
padding: 0 20px;
padding-top: ${({ insets: { top } }) => top}px;
padding-bottom: ${({ insets: { bottom } }) => bottom}px;
`;
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๊ถŒํ•œ ์š”์ฒญ, ์‚ฌ์ง„์˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
### ๐Ÿƒ๊ถŒํ•œ ์š”์ฒญ(iOS)
- expo-image-picker ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด์„œ ๊ธฐ๊ธฐ์˜ ์‚ฌ์ง„์ฒฉ์— ์ ‘๊ทผํ•ด์„œ ์„ ํƒ๋œ ์‚ฌ์ง„์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
- iOS์—์„œ๋Š” ์‚ฌ์ง„์ฒฉ์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋ฏ€๋กœ, ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๋Š” ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์•ˆ๋“œ๋กœ๋“œ์—์„œ๋Š” ํŠน๋ณ„ํ•œ ์„ค์ • ์—†์ด ์‚ฌ์ง„์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

```javascript
//install
expo install expo-image-picker

//import
import * as ImagePicker from "expo-image-picker";
import * as Permissions from "expo-permissions";

//iOS ๊ถŒํ•œ ์š”์ฒญ
useEffect(() => {
async () => {
try {
if (Platform.OS === "ios") {
const { status } = await Permissions.askAsync(
Permissions.CAMERA_ROLL
);
if (status !== "granted") {
Alert.alert(
"Photo Permission",
"Please turn on the camera roll permissions"
);
}
}
} catch (e) {
Alert.alert("Photo Permission Error", e.message);
}
};
}, []);
```

### ๐Ÿƒ์‚ฌ์ง„ ์ž…๋ ฅ๋ฐ›๊ธฐ
- ์‚ฌ์ง„ ๋ณ€๊ฒฝ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์—์„œ ๊ธฐ๊ธฐ์˜ ์‚ฌ์ง„์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœ๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’๋“ค์„ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
1. mediaTypes: ์กฐํšŒํ•˜๋Š” ์ž๋ฃŒ์˜ ํƒ€์ž…
2. allowsEditing: ์ด๋ฏธ์ง€ ์„ ํƒ ํ›„ ํŽธ์ง‘ ๋‹จ๊ณ„ ์ง„ํ–‰ ์—ฌ๋ถ€
3. aspect: ์•ˆ๋“œ๋กœ์ด๋“œ ์ „์šฉ ์˜ต์…˜์œผ๋กœ ์ด๋ฏธ์ง€ ํŽธ์ง‘์‹œ ์‚ฌ๊ฐํ˜•์˜ ๋น„์œจ([x, y])
4. quality: 0 ~ 1 ์‚ฌ์ด์˜ ๊ฐ’์„ ๋ฐ›์œผ๋ฉฐ ์••์ถ• ํ’ˆ์งˆ์„ ์˜๋ฏธ (1: ์ตœ๋Œ€ ํ’ˆ์งˆ)

```javascript
const _handelEditButton = async () => {
try {
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.images,
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
if (!result.cancelled) {
onChangeImage(result.uri);
}
} catch (e) {
Alert.alert("Photo Error", e.message);
}
};
```

- ๊ธฐ๊ธฐ์˜ ์‚ฌ์ง„์— ์ ‘๊ทผํ•˜๋Š” ํ•จ์ˆ˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ, cancelled ๊ฐ’์„ ํ†ตํ•ด ์„ ํƒ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์„ ํƒํ–ˆ๋‹ค๋ฉด ๋ฐ˜ํ™˜๋œ ๊ฒฐ๊ณผ์˜ uri๋ฅผ ํ†ตํ•ด ์„ ํƒ๋œ ์‚ฌ์ง„์˜ ์ฃผ์†Œ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

```json
//์ƒ๋‹จ์— result์˜ ๋ฐ˜ํ™˜ ๊ฐ’
{
"cancelled": true,
}

{
"cancelled": false,
"height": 000,
"type": "image",
"uri": "file:../...jpg",
"width": 000,
}
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ ๊ตฌํ˜„
### ๐Ÿƒ ๋กœ๊ทธ์ธ
- ํŒŒ์ด์–ด๋ฒ ์ด์Šค๋ฅผ ์ด์šฉํ•œ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ๋ฐ›๋Š” ํ•จ์ˆ˜๋Š” **signInWithEmailAndPassword** ์ž…๋‹ˆ๋‹ค.

```javascript
//utils/firebase.js
export const login = async ({ email, password }) => {
const { user } = await Auth.signInWithEmailAndPassword(email, password);
return user;
};
```


```javascript
//screens/Login.js
import { login } from "../utils/firebase";

const _handleLoginButtonPress = async () => {
try {
const user = await login({ email, password });
Alert.alert("Login Success", user.email);
} catch (e) {
Alert.alert("Login Error", e.message);
}
};
```

### ๐Ÿƒ ํšŒ์›๊ฐ€์ž…
- ํŒŒ์ด์–ด๋ฒ ์ด์Šค๋ฅผ ์ด์šฉํ•œ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜๋Š” **createUserWithEmailAndPassword** ์ž…๋‹ˆ๋‹ค.

```javascript
//utils/firebase.js

export const signup = async ({ email, password, name, photoUrl }) => {
const { user } = await Auth.createUserWithEmailAndPassword(email, password);
return user;
};
```

```javascript
//screens/Signup.js
import { signup } from "../utils/firebase";

const _handleSignupButtonPress = async () => {
try {
const user = await signup({ email, password, name, photoUrl });
console.log(user);
Alert.alert("Signup Success", user.email);
} catch (e) {
Alert.alert("Signup Error", e.message);
}
};
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Spinner(with ContextAPI)
### ๐Ÿƒ Spinner Component
- Spinner ์ปดํฌ๋„ŒํŠธ๋Š” ๋กœ๊ทธ์ธ ํ˜น์€ ํšŒ์›๊ฐ€์ž…์ด ์ง„ํ–‰๋˜๋Š” ๋™์•ˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ๋ฒ„ํŠผ์„ ์ถ”๊ฐ€๋กœ ํด๋ฆญํ•˜๋Š” ์ผ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ฐฉ์ง€ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•œ๋‹ค.
- Spinner ์ปดํฌ๋„ŒํŠธ๋Š” ๋ฆฌ์•กํŠธ ๋„ค์ดํ‹ฐ๋ธŒ์—์„œ ์ œ๊ณตํ•˜๋Š” **AcitivityIndicator** ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
- Spinner ์ปดํฌ๋„ŒํŠธ๋ฅผ AuthStack ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ํฌํ•จํ•œ ํ™”๋ฉด ์ „์ฒด๋ฅผ ์ฐจ์ง€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ํฌํ•จํ•œ ํ™”๋ฉด ์ „์ฒด๋ฅผ ๊ฐ์‹ธ๊ธฐ ์œ„ํ•ด์„œ๋Š” navigations ํด๋”์˜ index.js์—์„œ AuthStack ๋‚ด๋น„๊ฒŒ์ด์…˜๊ณผ ๊ฐ™์€ ์œ„์น˜์— Spinner ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.

```javascript
import React, { useContext } from 'react';
import { ActivityIndicator } from 'react-native';
import styled, { ThemeContext } from 'styled-components/native';

const Container = styled.View`
(...)
`;

const Spinner = () => {
const theme = useContext(ThemeContext);
return (



)
};

export default Spinner;
```

- Spinner ์ปดํฌ๋„ŒํŠธ๋ฅผ AuthStack ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ํฌํ•จํ•œ ํ™”๋ฉด ์ „์ฒด๋ฅผ ์ฐจ์ง€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด๋น„๊ฒŒ์ด์…˜์„ ํฌํ•จํ•œ ํ™”๋ฉด ์ „์ฒด๋ฅผ ๊ฐ์‹ธ๊ธฐ ์œ„ํ•ด์„œ๋Š” navigations ํด๋”์˜ index.js์—์„œ AuthStack ๋‚ด๋น„๊ฒŒ์ด์…˜๊ณผ ๊ฐ™์€ ์œ„์น˜์— Spinner ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ๋ฉ๋‹ˆ๋‹ค.

```javascript
(...)
const Navigation = () => {
const { inProgress } = useContext(ProgressContext);

return (


{inProgress && }

);
};
(...)
```

### ๐Ÿƒ Context API
- createContext ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด Context๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Provider ์ปดํฌ๋„ŒํŠธ์˜ value์— Spinner ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•  inPrgress ์ƒํƒœ ๋ณ€์ˆ˜์™€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

```javascript
//contexts/Progress.js

import React, { useState, createContext } from 'react';

//Context ์ƒ์„ฑ
const ProgressContext = createContext({
inProgress: false,
spinner: () => {},
});

//ProgressProvider
const ProgressProvider = ({ children }) => {
const [inProgress, setInProgress] = useState(false);

const spinner = {
start: () => setInProgress(true),
stop: () => setInProgress(false),
};
const value = { inProgress, spinner };

return (

{children}

);
};

export { ProgressContext, ProgressProvider };
```

```javascript
//contexts/index.js
import {ProgressContext, ProgressProvider } from './Progress';
export { ProgressContext, ProgressProvider };
```

```javascript
//src/App.js






```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Stack ๋‚ด๋น„๊ฒŒ์ด์…˜ ์† Tab ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํ—ค๋” ๋ณ€๊ฒฝ
- MainStack ๋‚ด๋น„๊ฒŒ์ด์…˜์—์„œ MainTab ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ํ™”๋ฉด์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” Screen ์ปดํฌ๋„ŒํŠธ์˜ name์€ "Main"์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค. ํ—ค๋”์˜ ํƒ€์ดํ‹€๊ณผ ๊ด€๋ จํ•ด ํŠน๋ณ„ํžˆ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด Screen ์ปดํฌ๋„ŒํŠธ์˜ name์— ์„ค์ •๋œ ๊ฐ’์ด ํ—ค๋”์˜ ํƒ€์ดํ‹€๋กœ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ํ”„๋กœํ•„ ํ™”๋ฉด๊ณผ ์ฑ„๋„ ๋ชฉ๋ก ๋ชจ๋‘ 'Main'์œผ๋กœ ํƒ€์ดํ‹€์ด ๋‚˜ํƒ€๋‚œ๋‹ค.
```javascript


(...)

```

- MainTab ๋‚ด๋น„๊ฒŒ์ด์…˜์€ MainStack ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํ™”๋ฉด์œผ๋กœ ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ํ™”๋ฉด๋“ค๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ props๋ฅผ ํ†ตํ•ด navigation๊ณผ route๋ฅผ ์ „๋‹ฌ ๋ฐ›๋Š”๋‹ค.
- route์— ํฌํ•จ๋œ state์˜ ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค
1. index: ํ˜„์žฌ ๋ Œ๋”๋ง ๋˜๋Š” ํ™”๋ฉด์˜ ์ธ๋ฑ์Šค
2. routeNames: ํ™”๋ฉด์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” Navigator ์ปดํฌ๋„ŒํŠธ์—์„œ Screen ์ปดํฌ๋„ŒํŠธ๋“ค์˜ name ์†์„ฑ์„ ๋ฐฐ์—ด๋กœ ๊ฐ–๋Š”๋‹ค.
3. type: ํ˜„์žฌ ํ™”๋ฉด์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” Navigator ์ปดํฌ๋„ŒํŠธ์˜ ํƒ€์ž…์ด๋ฉฐ, MainTab ๋‚ด๋น„๊ฒŒ์ด์…˜์€ ํƒญ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด๊ธฐ ๋•Œ๋ฌธ์— 'tab' ๊ฐ’์„ ๊ฐ–๋Š”๋‹ค.

```json
//route์˜ state
{
"index": 0,
"routeNames": [
"Channel List",
"Profile",
],
"type": "tab",
...
}
```

```javascript
//MainTab
useEffect(() => {
const titles = route.state?.routeNames || ['Channels'];
const index = route.state?.index || 0;
navigation.setOptions({ headerTitle: titles[index ]});
}, [route]);
```


- ํ•˜์ง€๋งŒ ์œ„์— ๋ฐฉ์‹๋Œ€๋กœ ํ•˜๋ฉด route์˜ state์— ์ง์ ‘ ์ ‘๊ทผํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ๊ณ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค. ์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด **getFocusedRouteNameFromRoute** ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
- ๐Ÿ”– ๊ด€๋ จ ์ด์Šˆ: https://github.com/Alchemist85K/my-first-react-native/discussions/26

```javascript
useEffect(() => {
const screenName = getFocusedRouteNameFromRoute(route) || 'Channels';

navigation.setOptions({
headerTitle: screenName,
});
}, [route]);
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป Setting a timer for a long period of time, ... ์˜ค๋ฅ˜
- ๐Ÿ”– ๊ด€๋ จ ์ด์Šˆ: https://github.com/Alchemist85K/my-first-react-native/discussions/28
- /node_modules/react-native/Libraries/Core/Timers/JSTimers.js
- MAX_TIMER_DURATION_MS ๋ผ๋Š” ๋ณ€์ˆ˜ ๊ฐ’์„ 60 * 1000 ์—์„œ 10000 * 1000์œผ๋กœ ๋ณ€๊ฒฝ


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
- ํŒŒ์ด์–ด๋ฒ ์ด์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” ํŒŒ์ด์–ด์Šคํ† ์–ด๋Š” NoSQL ๋ฌธ์„œ ์ค‘์‹ฌ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ด๋‹ค.
- SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋‹ฌ๋ฆฌ ํ…Œ์ด๋ธ”์ด๋‚˜ ํ–‰์ด ์—†๊ณ  ์ปฌ๋ ‰์…˜, ๋ฌธ์„œ, ํ•„๋“œ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.
1. ์ปฌ๋ ‰์…˜์€ ๋ฌธ์„œ์˜ ์ปจํ…Œ์ด๋„ˆ ์—ญํ• ์„ ํ•˜๋ฉฐ, ๋ชจ๋“  ๋ฌธ์„œ๋Š” ํ•ญ์ƒ ์ปฌ๋ ‰์…˜์— ์ €์žฅ๋œ๋‹ค.
2. ๋ฌธ์„œ๋Š” ํŒŒ์ด์–ด์Šคํ† ์–ด์˜ ์ €์žฅ ๋‹จ์œ„๋กœ ๊ฐ’์ด ์žˆ๋Š” ํ•„๋“œ๋ฅผ ๊ฐ–๋Š”๋‹ค. ๋ฌธ์„œ์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง•์€ ์ปฌ๋ ‰์…˜์„ ํ•„๋“œ๋กœ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
- ํŒŒ์ด์–ด์Šคํ† ์–ด๋Š” ์ผ๋ฐ˜์ ์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋‹ฌ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๋‚ด์šฉ์ด ์ˆ˜์ •๋˜๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
- ์ปฌ๋ ‰์…˜๊ณผ ๋ฌธ์„œ๋Š” ํ•ญ์ƒ ์œ ์ผํ•œ ID๋ฅผ ๊ฐ–๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ทœ์น™์ด ์žˆ๋‹ค.

![1](https://user-images.githubusercontent.com/64779472/114672004-a44de180-9d3f-11eb-9646-eaa072f40f2c.PNG)

```
//ํŒŒ์ด์–ด์Šคํ† ์–ด ๋ณด์•ˆ ๊ทœ์น™ ์ˆ˜์ •
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /channels/{channel} {
allow read, write: if request.auth.uid != null;
}
}
}
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป FlatList
- ์ง€๊ธˆ๊นŒ์ง€ ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋งํ•  ๋•Œ ScrollView ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•ด ํ™”๋ฉด์ด ๋„˜์–ด๊ฐ€๋„๋ก ์Šคํฌ๋กค์ด ์ƒ์„ฑ๋˜์–ด ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
- FlatList์ปดํฌ๋„ŒํŠธ๋Š” ScrollView ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜๋Š”๋ฐ, ScrollView ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ฐ์ดํ„ฐ์˜ ์–‘์„ ์•Œ๊ณ  ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ณ , ๋ฐ์ดํ„ฐ๊ฐ€ ๋งค์šฐ ๋งŽ์œผ๋ฉด ๋ Œ๋”๋ง ์†๋„๊ฐ€ ๋А๋ ค์ง€๊ณ  ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ฆ๊ฐ€ํ•˜๋Š” ๋“ฑ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.
- ๊ทธ์—๋ฐ˜ํ•ด, FlatList๋Š” ํ™”๋ฉด์— ์ ์ ˆํ•œ ์–‘์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋ Œ๋”๋งํ•˜๊ณ  ์Šคํฌ๋กค์˜ ์ด๋™์— ๋งž์ถฐ ํ•„์š”ํ•œ ๋ถ€๋ถ„์„ ์ถ”๊ฐ€์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๋Š” ํŠน์ง•์ด ์žˆ์Šต๋‹ˆ๋‹ค.
- FlatList์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ 3๊ฐ€์ง€ ์†์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
1. data: ์ฒ˜์Œ ๋ Œ๋”๋งํ•  ํ•ญ๋ชฉ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌํ•œ๋‹ค.
2. renderItem: ์ „๋‹ฌ๋œ ๋ฐฐ์—ด์˜ ํ•ญ๋ชฉ์„ ์ด์šฉํ•ด ๋ Œ๋”๋งํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
3. keyExtractor: key๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ์œ ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค.


```js
item['id'].toString()}
data={channels}
renderItem={({ item }) => {
return (

)
}}
windowSize={3}
/>
```


### ๐Ÿƒ windowSize
- FlatList์—์„œ ๋ Œ๋”๋ง ๋˜๋Š” ๋ฐ์ดํ„ฐ์˜ ์–‘์„ ์กฐ์ ˆํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด windowSize ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด์„œ ๊ฐ’์„ ์›ํ•˜๋Š” ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.
- windowSize์˜ ๊ฐ’์„ ์ž‘์€ ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ Œ๋”๋ง๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ค„์–ด๋“ค์–ด ๋ฉ”๋ชจ๋ฆฌ์˜ ์†Œ๋น„๋ฅผ ์ค„์ด๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋น ๋ฅด๊ฒŒ ์Šคํฌ๋กคํ•˜๋Š” ์ƒํ™ฉ์—์„œ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์€ ์ˆœ๊ฐ„์ ์œผ๋กœ ๋นˆ ๋‚ด์šฉ์ด ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.


```js

```


### ๐Ÿƒ React.Memo
- React.Memo๋Š” useMemo Hook ํ•จ์ˆ˜์™€ ๋น„์Šทํ•˜์ง€๋งŒ, ๋ถˆํ•„์š”ํ•œ ํ•จ์ˆ˜์˜ ์žฌ์—ฐ์‚ฐ์„ ๋ฐฉ์ง€ํ•˜๋Š” useMemo์™€ ๋‹ฌ๋ฆฌ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•œ๋‹ค๋Š” ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.
- React.Memo๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ„๋‹จํžˆ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
- React.Memo๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด Item ์ปดํฌ๋„ŒํŠธ๋Š” props๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๊นŒ์ง€ ๋ฆฌ๋ Œ๋”๋ง๋˜์ง€ ์•Š๋Š”๋‹ค.


```js
const Item = React.memo(({ item: { id, title, description, createdAt }, onPress }) => {
const theme = useContext(ThemeContext);
console.log(`Item ${id}`);

return (
onPress({ id, title })}>

{title}
{description}

{createdAt}


);
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ์ฑ„๋„ ๋ฐ์ดํ„ฐ ์ˆ˜์‹ 
- firebase์˜ Cloud Firestore์—์„œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” onSnapshop ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
- **onSnapshop** ๋ฉ”์„œ๋“œ๋Š” ์ˆ˜์‹  ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ์žˆ๋‹ค๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฌธ์„œ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์ˆ˜์ •๋  ๋•Œ๋งˆ๋‹ค ์ง€์ •๋œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
- ์ด๋•Œ, ์˜ค๋ฆ„์ฐจ์ˆœ, ๋‚ด๋ฆผ์ฐจ์ˆœ์€ **orderBy** ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


```js
useEffect(() => {
const unsubscribe = DB.collection('channels')
.orderBy('createdAt', 'desc')
.onSnapshot(snapshot => {
const list = [];

snapshot.forEach(doc => {
list.push(doc.data());
});
setChannels(list);
});

return () => unsubscribe();
}, []);
```


## ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป react-native-gifted-chat
- ์ฑ„ํŒ… ํ™”๋ฉด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๋‹ค์–‘ํ•˜๊ฒŒ ์ œ๊ณตํ•˜๋Š” react-native-gifted-chat ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
- react-native-gifted-chat ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ GiftedChat ์ปดํฌ๋„ŒํŠธ๋Š” ๋‹ค์–‘ํ•œ ์„ค์ •์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ๋งŽ์€ ์†์„ฑ์„ ์ œ๊ณตํ•œ๋‹ค.
1. ์ž…๋ ฅ๋œ ๋‚ด์šฉ์„ ์„ค์ •๋œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด ๋ฐ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ ID์™€ ํ•จ๊ป˜ ์ „๋‹ฌ ํ•˜๋Š” ๊ธฐ๋Šฅ
2. ์ „์†ก ๋ฒ„ํŠผ์„ ์ˆ˜์ •ํ•˜๋Š” ๊ธฐ๋Šฅ
3. ์Šคํฌ๋กค์˜ ์œ„์น˜์— ๋”ฐ๋ผ ์Šคํฌ๋กค ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฒ„ํŠผ ๋ Œ๋”๋ง


```js

}
/>

```


- user์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด๋‘๋ฉด onSend์— ์ •์˜ํ•œ ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ์ž…๋ ฅ๋œ ๋ฉ”์‹œ์ง€์™€ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

```
User {
_id: string | number;
name: string;
avatar: string | renderFunction;
}
```


```
Message {
_id: string | number;
text: string;
createdAt: Date | number;
user: User;
...
}
```