• Stars
    star
    175
  • Rank 210,344 (Top 5 %)
  • Language
    Objective-C
  • Created over 2 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Delivery icons created by dreamicons - Flaticon

์ฒซ ์‹œ์ž‘(setting)

๊ณต์‹๋ฌธ์„œ

# ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ํด๋”๋กœ ์ด๋™
npx react-native init FoodDeliveryApp

# ๋‹ค์Œ ๋ง์ด ๋œจ๋ฉด y ์ž…๋ ฅ
Need to install the following packages:
  [email protected]
Ok to proceed? (y)

# mac์ด๋ฉด ๋‹ค์Œ ์ฝ”๋“œ๋„ ์ž…๋ ฅ(๋งฅ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ํ•„์š”)
sudo gem install cocoapods
# cocoapods ์„ค์น˜์‹œ ruby ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋ฉด ํ„ฐ๋ฏธ๋„์— ๋‚˜์˜ค๋Š” ๋ช…๋ น์–ด ์ž…๋ ฅ ํ›„ cocoapods ์žฌ์„ค์น˜(์•„๋ž˜ ๋ฒ„์ „์€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฃผ์˜)
sudo gem install activesupport -v 6.1.7.6

# ๋งˆ์ง€๋ง‰์œผ๋กœ pod ์„ค์น˜
cd ./FoodDeliveryApp/ios && pod install

์ž ๊น!! ์ด ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ•ญ์ƒ ์ตœ์‹  ๋ฒ„์ „์˜ react๋ฅผ ๋ฐ›์•„์˜ค๋ฏ€๋กœ ๊ฐ•์ขŒ์˜ ๋ฒ„์ „(0.66)๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š๊ฒŒ ๋จ. ํ˜„์žฌ ์ตœ์‹  ๋ฒ„์ „์€ 0.72์ด๋ผ์„œ ์ƒ๋‹นํžˆ ์ฐจ์ด๊ฐ€ ๋‚จ. ๊ฐ•์ขŒ๋ž‘ ๋™์ผํ•œ ๋ฒ„์ „์œผ๋กœ ํ•˜์ง€ ์•Š์œผ๋ฉด ๋งŽ์€ ์ŠคํŠธ๋ ˆ์Šค๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ. ๊ฐ•์ขŒ๋ž‘ ๋™์ผํ•œ ๋ฒ„์ „์œผ๋กœ ํ•˜๋ ค๋ฉด ์ด๋ฏธ ์ดˆ๋ฐ˜ ์„ธํŒ…์ด ๋‹ค ๋˜์–ด ์žˆ๋Š” setting ํด๋”๋ฅผ git clone๋ฐ›์•„ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ(ํด๋ก  ํ›„ npm i && npx pod-install ์ˆ˜ํ–‰ ํ•„์š”). 0.71์ด๋‚˜ 0.72 ๋ฒ„์ „ ์†Œ์Šค์ฝ”๋“œ๋„ ๊นƒํ—™์— ์กด์žฌํ•˜๋‹ˆ ๊ฐ๊ฐ์˜ ํด๋” ์ฐธ๊ณ . 0.72๋ฒ„์ „์œผ๋กœ ํ•ด๋„ ์ž˜ ๋Œ์•„๊ฐ€๊ธด ํ•จ.

๋ณดํ†ต์€ ๊ฐ•์˜์šฉ์œผ๋กœ ์ž๋™์ƒ์„ฑ ์•ˆ ์ข‹์•„ํ•˜๋Š”๋ฐ RN์€ ์ž๋™์ƒ์„ฑํ•˜์ง€ ์•Š์œผ๋ฉด ๋„ค์ดํ‹ฐ๋ธŒ๋‹จ๊นŒ์ง€ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์›€

ํ”„๋กœ์ ํŠธ ํด๋”์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ์•ฑ ์‹คํ–‰ ๊ฐ€๋Šฅ

npm run android # ์•ˆ๋“œ๋กœ์ด๋“œ ์‹คํ–‰ ๋ช…๋ น์–ด
npm run ios # ์•„์ดํฐ ์‹คํ–‰ ๋ช…๋ น์–ด

์„œ๋ฒ„๊ฐ€ ํ•˜๋‚˜ ๋œฐ ๊ฒƒ์ž„. Metro ์„œ๋ฒ„. ์—ฌ๊ธฐ์„œ ์„œ๋ฒ„๊ฐ€ ์•ˆ ๋œจ๊ณ  No device ๋“ฑ์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค๋ฉด ์•ˆ๋“œ๋กœ์ด๋“œ ์—๋ฎฌ๋ ˆ์ดํ„ฐ ์‹คํ–‰ํ•œ ์ฑ„๋กœ ๋‹ค์‹œ ๋ช…๋ น์–ด ์ž…๋ ฅํ•  ๊ฒƒ. Metro ์„œ๋ฒ„์—์„œ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผํ•˜๊ณ  ์•ฑ์œผ๋กœ ์ „์†กํ•ด์คŒ. ๊ธฐ๋ณธ 8081ํฌํŠธ. ๋ฉ”ํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ๊บผ์ ธ์žˆ๋‹ค๋ฉด ํ„ฐ๋ฏธ๋„์„ ํ•˜๋‚˜ ๋” ์—ด์–ด

npm start

๊ฐœ๋ฐœ์€ iOS ๊ธฐ์ค€์œผ๋กœ ํ•˜๋Š” ๊ฒŒ ์ข‹๋‹ค(๊ฐœ์ธ ๊ฒฝํ—˜). ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ•์ขŒ๋Š” ์–ด์ฉ” ์ˆ˜ ์—†์ด Windows๋กœ ํ•œ๋‹ค.

[email protected] ๋ฒ„์ „, ํ•œ ๋‹ฌ์— 0.1์”ฉ ์˜ฌ๋ผ๊ฐ€๋Š”๋ฐ ์š”์ฆ˜ ๊ฐœ๋ฐœ ์†๋„๊ฐ€ ๋Š๋ ค์ ธ์„œ ๊ทœ์น™์ด ๊นจ์ง. ๊ฑฐ์˜ ์™„์„ฑ ๋‹จ๊ณ„๋ผ ์‹ ๊ทœ ๊ธฐ๋Šฅ์€ npm์—์„œ @react-native-community๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์•ผ ํ•จ. ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•จ๋ถ€๋กœ ํ•˜์ง€ ๋ง ๊ฒƒ!

[๋งฅ ์ „์šฉ]npx pod-install๋„ ๋ฏธ๋ฆฌ ํ•œ ๋ฒˆ, iOS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(pod) ๋ฐ›๋Š” ์šฉ๋„

ํด๋” ๊ตฌ์กฐ

  • android: ์•ˆ๋“œ๋กœ์ด๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ํด๋”
  • ios: ios ๋„ค์ดํ‹ฐ๋ธŒ ํด๋”
  • node_modules: ๋…ธ๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • app.json: name์€ ์•ฑ ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„์ด๋‹ˆ ํ•จ๋ถ€๋กœ ๋ฐ”๊พธ๋ฉด ์•ˆ ๋จ, ์ด๊ฑฐ ๋ฐ”๊พธ๋ฉด ๋„ค์ดํ‹ฐ๋ธŒ ์ปดํฌ๋„ŒํŠธ ์ด๋ฆ„๋„ ๋‹ค ๋ฐ”๊ฟ”์•ผํ•จ, displayName์€ ์•ฑ ์ด๋ฆ„ ๋ณ€๊ฒฝ์šฉ
    • ios/FoodDeliveryApp/AppDelegate.m ์˜ moduleName
    • android/app/src/main/java/com/fooddeliveryapp/MainActivity.java ์˜ getMainComponentName
  • babel.config.js: ๋ฐ”๋ฒจ ์„ค์ •
  • index.js: ๋ฉ”์ธ ํŒŒ์ผ
  • App.tsx: ๊ธฐ๋ณธ App ์ปดํฌ๋„ŒํŠธ
  • metro.config.js: ๋ฉ”ํŠธ๋กœ ์„ค์ • ํŒŒ์ผ(์›นํŒฉ ๋Œ€์‹  ์‚ฌ์šฉ)
  • tsconfig.json: ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์„ค์ •
  • android/app/src/main/java/com/fooddeliveryapp/MainActivity.java: ์•ˆ๋“œ๋กœ์ด๋“œ ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ js์—”์ง„ ํ†ตํ•ด ๋ฆฌ์•กํŠธ ์ฝ”๋“œ ์‹คํ–‰ + bridge๋กœ ์†Œํ†ต

์•ฑ ์‹คํ–‰ ํ›„

  • cmd + R๋กœ ๋ฆฌ๋กœ๋”ฉ
  • cmd + D๋กœ ๋””๋ฒ„๊ทธ ๋ฉ”๋‰ด
  • Debugging with Chrome์œผ๋กœ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • Configure Bundler๋กœ ๋ฉ”ํŠธ๋กœ ์„œ๋ฒ„ ํฌํŠธ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  • Show Perf Monitor๋กœ ํ”„๋ ˆ์ž„ ์ธก์ • ๊ฐ€๋Šฅ

Flipper ํŽ˜์ด์Šค๋ถ์ด ๋งŒ๋“  ๋ชจ๋ฐ”์ผ์•ฑ ๋””๋ฒ„๊ฑฐ๋„ ์ข‹์Œ(๋‹ค๋งŒ ์—ฐ๊ฒฐ ์‹œ ์—๋Ÿฌ๋‚˜๋Š” ์‚ฌ๋žŒ ๋‹ค์ˆ˜ ๋ฐœ๊ฒฌ)

  • [ios]์„ค์น˜ ์‹œ ํ™˜๊ฒฝ์„ค์ • -> ๊ฐœ์ธ์ •๋ณด ๋ฐ ๋ณด์•ˆ ๋ฉ”๋‰ด์—์„œ Flipper๋ฅผ ํ—ˆ์šฉํ•ด์ฃผ์–ด์•ผ ํ•จ
  • troubleshoot -> setup doctor ๋ฌธ์ œ ํ•ด๊ฒฐํ•  ๊ฒƒ
npm i react-native-flipper redux-flipper rn-flipper-async-storage-advanced @react-native-async-storage/async-storage
npx pod-install # ์•„์ดํฐ ์ „์šฉ
  • flipper-plugin-async-storage-advanced
  • flipper-plugin-redux-debugger
  • Layout, Network, Images, Database(sqlite), React Devtools, Hermes Debugger ์‚ฌ์šฉ ๊ฐ€๋Šฅ

์•ฑ ์ด๋ฆ„ ๋ณ€๊ฒฝ

\android\app\src\main\res\values\strings.xml

app.json์˜ displayName

\ios\FoodDeliveryApp\Info.plist์˜ CFBundleDisplayName

๋‹จ! 0.68๋ฒ„์ „๋ถ€ํ„ฐ๋Š” app.json, strings.xml, CFBundleDisplayName์„ ํ•œ๊ธ€๋กœํ•˜๋ฉด ํŠ•๊ธฐ๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ. ๊ทธ๋Ÿด๋•Œ๋Š” ์ „๋ถ€ ์˜์–ด๋กœ ๋˜๋Œ๋ฆฌ๊ณ  ios์—์„œ๋Š” ๋งํฌ ๋”ฐ๋ผ์„œ ๋‹ค๊ตญ์–ด ์„ค์ •์œผ๋กœ ํ•œ๊ตญ์–ด ์„ค์ •ํ•  ๊ฒƒ. ๋˜ํ•œ ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ๋Š” \android\app\src\main\res\values\strings.xml์€ ์˜์–ด๋กœ ๋‘๊ณ  \android\app\src\main\res\values-ko\strings.xml ์„ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด ์—ฌ๊ธฐ์„œ ํ•œ๊ธ€๋กœ ๋ณ€๊ฒฝํ•  ๊ฒƒ

android/gradle.properties

FLIPPER_VERSION=0.145.0

ํ”Œ๋ฆฌํผ ๋ฒ„์ „์ด 0.145.0๋ณด๋‹ค ๋‚ฎ๋‹ค๋ฉด 0.145.0์œผ๋กœ ๋†’์ผ ๊ฒƒ. RN 0.72๋ฒ„์ „์—์„œ๋Š” ์ด๋ฏธ 0.182.0์ž„

๋ฆฌ์•กํŠธ ๋„ค์ดํ‹ฐ๋ธŒ ํด๋” ๊ตฌ์กฐ

  • src ํด๋” ์ƒ์„ฑ(์ง€๊ธˆ ๋ฐ”๋กœ ์ƒ์„ฑ ์•ˆ ํ•˜๊ณ  ํด๋” ์•ˆ์— ํŒŒ์ผ์ด ๋“ค ๋•Œ ์ƒ์„ฑํ•ด๋„ ๋จ)
  • src/assets: ์ด๋ฏธ์ง€, ํฐํŠธ ๋“ฑ
  • src/constants: ์ƒ์ˆ˜
  • src/pages: ํŽ˜์ด์ง€ ๋‹จ์œ„ ์ปดํฌ๋„ŒํŠธ
  • src/components: ๊ธฐํƒ€ ์ปดํฌ๋„ŒํŠธ
  • src/contexts: context api ๋ชจ์Œ
  • src/hooks: ์ปค์Šคํ…€ ํ›… ๋ชจ์Œ
  • src/modules: ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ
  • src/store: ๋ฆฌ๋•์Šค ์Šคํ† ์–ด ์„ธํŒ…
  • src/slices: ๋ฆฌ๋•์Šค ์Šฌ๋ผ์ด์Šค
  • types: ํƒ€์ž… ์ •์˜

์ฝ”๋”ฉ ์‹œ์ž‘!

App.tsx ๋ถ„์„

  • View๊ฐ€ div, Text๊ฐ€ span์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ธฐ(1๋Œ€1 ๋งค์นญ์€ ์•„๋‹˜)
  • css๋Š” dp ๋‹จ์œ„(density-independent pixels, ๋‹ค์–‘ํ•œ ํ™”๋ฉด ํฌ๊ธฐ์— ์˜ํ–ฅ๋ฐ›์ง€ ์•Š์Œ)
  • css ์†์„ฑ ๋ฆฌ์ŠคํŠธ: ์ข€ ์˜ค๋ž˜๋จ
  • flex์—์„œ๋Š” flexDirection์ด Column์ด default

React Navigation

react-router-native๋„ ๋Œ€์•ˆ์ž„(์›น์—์„œ ๋„˜์–ด์˜จ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ์นœ์ˆ™, ์›น์ฒ˜๋Ÿผ ์ฃผ์†Œ ๊ธฐ๋ฐ˜)

npm i @react-navigation/native
npm i @react-navigation/native-stack
npm i react-native-screens react-native-safe-area-context
npx pod-install # ๋งฅ ์ „์šฉ

android/app/src/main/java/FoodDeliveryApp/MainActivity.java

import android.os.Bundle;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(null);
}

android/build.gradle

buildscript {
    ext {
        ...
        kotlin_version = '1.6.10'
    }
    ...
    dependencies {
        ...
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
    ...
}

App.tsx ๊ต์ฒด

import * as React from 'react';
import {NavigationContainer, ParamListBase} from '@react-navigation/native';
import {
  createNativeStackNavigator,
  NativeStackScreenProps,
} from '@react-navigation/native-stack';
import {Text, TouchableHighlight, View} from 'react-native';
import {useCallback} from 'react';

type RootStackParamList = {
  Home: undefined;
  Details: undefined;
};
type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
type DetailsScreenProps = NativeStackScreenProps<ParamListBase, 'Details'>;

function HomeScreen({navigation}: HomeScreenProps) {
  const onClick = useCallback(() => {
    navigation.navigate('Details');
  }, [navigation]);

  return (
          <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
            <TouchableHighlight onPress={onClick}>
              <Text>Home Screen</Text>
            </TouchableHighlight>
          </View>
  );
}

function DetailsScreen({navigation}: DetailsScreenProps) {
  const onClick = useCallback(() => {
    navigation.navigate('Home');
  }, [navigation]);

  return (
          <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
            <TouchableHighlight onPress={onClick}>
              <Text>Details Screen</Text>
            </TouchableHighlight>
          </View>
  );
}

const Stack = createNativeStackNavigator<RootStackParamList>();
function App() {
  return (
          <NavigationContainer>
            <Stack.Navigator initialRouteName="Home">
              <Stack.Screen
                      name="Home"
                      component={HomeScreen}
                      options={{title: 'Overview'}}
              />
              <Stack.Screen name="Details">
                {props => <DetailsScreen {...props} />}
              </Stack.Screen>
            </Stack.Navigator>
          </NavigationContainer>
  );
}

export default App;
  • safe-area๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ์Œ(์„ค๋ช…)
  • NavigationContainer: ๋‚ด๋น„๊ฒŒ์ด์…˜ ์ƒํƒœ ์ €์žฅ
  • Navigator ์•ˆ์— Screen๋“ค ๋ฐฐ์น˜
  • Screen name ๋Œ€์†Œ๋ฌธ์ž ์ƒ๊ด€ ์—†์Œ, component๋Š” ๋ณดํ†ต ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹ ์‚ฌ์šฉ(์ปดํฌ๋„ŒํŠธ ๊ทธ ์ž์ฒด vs Render Callback)
  • props๋กœ navigation๊ณผ route๊ฐ€ ์ „๋‹ฌ๋จ
  • Pressable, Button, TouchableHighlight, TouchableOpacity, TouchableWithoutFeedback, TouchableNativeFeedback
  • navigation.navigate๋กœ ์ด๋™ ๊ฐ€๋Šฅ
  • navigation.push๋กœ ์Œ“๊ธฐ ๊ฐ€๋Šฅ
  • navigation.goBack์œผ๋กœ ์ด์ „์œผ๋กœ ์ด๋™
  • params ์ถ”๊ฐ€ ๊ฐ€๋Šฅ(params์— user๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ํ†ต์งธ๋กœ ๋„ฃ์ง€ ๋ง๊ธฐ, id๋ฅผ ๋„ฃ๊ณ  user๋Š” ๊ธ€๋กœ๋ฒŒ ์Šคํ† ์–ด์— ๋„ฃ๊ธฐ)
  • Screen options.title: ์ œ๋ชฉ
  • Screen options์— ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด route.params๋กœ params ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • navigation.setOptions๋กœ ์˜ต์…˜ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
  • Navigator screenOptions๋กœ ๊ณตํ†ต ์˜ต์…˜ ์„ค์ •
  • Screen options.headerShown๋กœ ํ—ค๋”ํ‘œ์‹œ์—ฌ๋ถ€
  • Screen options.headerTitle๋กœ ์ปค์Šคํ…€ ์ปดํฌ๋„ŒํŠธ
  • Screen options.headerRight๋กœ ์šฐ์ธก ๋ฒ„ํŠผ(useLayoutEffect) ์˜ต์…˜ ๋ชฉ๋ก

์‹ค์ œ ๋ผ์šฐํ„ฐ ๋งŒ๋“ค๊ธฐ (ch1)

npm install @react-navigation/bottom-tabs

App.tsx

  • Tab.Navigator ๋„์ž…
  • isLoggedIn ๋ถ„๊ธฐ์ฒ˜๋ฆฌ
  • Drawer๊ณผ Tab.Group ์‚ฌ์šฉ์ฒ˜ ์†Œ๊ฐœ src/pages/Delivery.tsx
  • Navigator๋Š” nesting ๊ฐ€๋Šฅ

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ

src/components/DismissKeyBoardView.tsx

import React from 'react';
import {
  TouchableWithoutFeedback,
  Keyboard,
  StyleProp,
  ViewStyle,
  KeyboardAvoidingView,
  Platform,
} from 'react-native';

const DismissKeyboardView: React.FC<{ style: StyleProp<ViewStyle> }> = ({children, ...props}) => (
  <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
    <KeyboardAvoidingView
      {...props}
      style={props.style}
      behavior={Platform.OS === 'android' ? undefined : 'padding'}>
      {children}
    </KeyboardAvoidingView>
  </TouchableWithoutFeedback>
);

export default DismissKeyboardView;

์ธํ’‹ ๋ฐ”๊นฅ ํด๋ฆญ ์‹œ ํ‚ค๋ณด๋“œ๋ฅผ ๊ฐ€๋ฆฌ๊ธฐ ์œ„ํ•จ

  • src/pages/SignIn.tsx
  • src/pages/SignUp.tsx
  • src/components/DismissKeyboardView.tsx
  • TextInput, StyleSheet.compose ์‚ฌ์šฉ
  • DismissKeyboardView ๋งŒ๋“ค๊ธฐ(Keyboard, KeyboardAvoidingView)
  • KeyboardAvoidingView๋Š” ๋ถˆํŽธํ•จ
  • react-native-keyboard-aware-scrollview๋ฅผ ๋Œ€์•ˆ์œผ๋กœ ์‚ฌ์šฉ
npm i react-native-keyboard-aware-scrollview  
  • ํƒ€์ดํ•‘์ด ์—†์œผ๋ฏ€๋กœ ์ง์ ‘ ํƒ€์ž… ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
  • react-native-keyboard-aware-scroll-view ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ํƒ€์ž…์ด ์žˆ์Œ

types/react-native-keyboard-aware-scroll-view

src/components/DismissKeyBoardView.tsx

์„œ๋ฒ„ ์š”์ฒญ ๋ณด๋‚ด๊ธฐ(ch2)

back ์„œ๋ฒ„ ์‹คํ–‰ ํ•„์š”, DB ์—†์ด๋„ ๋˜๊ฒŒ๋” ๋งŒ๋“ค์–ด๋‘ . ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ๋Š” ๋‚ ์•„๊ฐ€๋‹ˆ ์ฃผ์˜

# ํ„ฐ๋ฏธ๋„ ํ•˜๋‚˜ ๋” ์ผœ์„œ
cd back
npm start

๋ฆฌ๋•์Šค ์„ค์ •

npm i @reduxjs/toolkit react-redux redux-flipper

src/store/index.ts์™€ src/store/reducer.ts, src/slices/user.ts ์ž‘์„ฑ

AppInner.tsx ์ƒ์„ฑ ๋ฐ isLoggedIn์„ redux๋กœ ๊ต์ฒด(AppInner ๋ถ„๋ฆฌ ์ด์œ ๋Š” App.tsx์—์„œ useSelector๋ฅผ ๋ชป ์”€)

App.tsx

import * as React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {Provider} from 'react-redux';
import store from './src/store';
import AppInner from './AppInner';

function App() {
  return (
    <Provider store={store}>
      <NavigationContainer>
        <AppInner />
      </NavigationContainer>
    </Provider>
  );
}

export default App;

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ

์•ก์„ธ์Šคํ† ํฐ/๋ฆฌํ”„๋ ˆ์‹œํ† ํฐ์„ ๋ฐ›์•„์„œ ๋‹ค์Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ €์žฅ

npm install react-native-encrypted-storage
npx pod-install # ios ์ „์šฉ

์„œ๋ฒ„ ์š”์ฒญ์€ axios ์‚ฌ์šฉ(์š”์ฆ˜ ky๋‚˜ got์œผ๋กœ ๋„˜์–ด๊ฐ€๋Š” ์ถ”์„ธ์ด๋‚˜ react-native์™€ ํ˜ธํ™˜ ์—ฌ๋ถ€ ๋ถˆํˆฌ๋ช…)

npm i axios

ํ™˜๊ฒฝ๋ณ€์ˆ˜, ํ‚ค ๊ฐ’์„ ์ €์žฅํ•  config ํŒจํ‚ค์ง€

npm i react-native-config
import Config from 'react-native-config';

-Android์—์„œ Config๊ฐ€ ์ ์šฉ์ด ์•ˆ ๋˜๋ฉด ๋‹ค์Œ ์ถ”๊ฐ€ํ•ด์•ผํ•จ

android/app/proguard-rules.pro

-keep class com.fooddeliveryapp.BuildConfig { *; }

android/app/build.gradle

apply plugin: "com.android.application"
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
...
    defaultConfig {
        ...
        resValue "string", "build_config_package", "com.fooddeliveryapp"
    }
  • .env์— ํ‚ค=๊ฐ’ ์ €์žฅํ•ด์„œ(์˜ˆ๋ฅผ ๋“ค์–ด abc=def) Config.abc๋กœ ๊บผ๋‚ด ์”€ .env
API_URL=http://10.0.2.2:3105
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•„์ดํ”ผ๋Š” 10.0.2.2๋กœ ํ•ด์•ผ ํ•จ(localhost๋กœ ํ•˜๋ฉด ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์•ˆ ๋จ)
  • 10.0.2.2๊ฐ€ ์•ˆ ๋˜๋ฉด ๋„ค์ด๋ฒ„์— ๋‚ด ์•„์ดํ”ผ ์ณ์„œ ์™ธ๋ถ€IP๋„ ์ž…๋ ฅํ•ด๋ณด๊ณ , ipconfig ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•  ๋•Œ ๋‚˜์˜ค๋Š” ๋‚ด๋ถ€IP๋„ ์ž…๋ ฅํ•ด์„œ ๋˜๋Š” ๊ฒƒ ์ฐพ๊ธฐ
  • ์—๋ฎฌ๋ ˆ์ดํ„ฐ/์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ/์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ผœ์„œ ์•„์ดํ”ผ:3105 ์ž…๋ ฅํ–ˆ์„ ๋•Œ ํŽ˜์ด์ง€๊ฐ€ ์ œ๋Œ€๋กœ ๋œจ๋Š” IP๊ฐ€ ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋Š” IP
  • [ios]์—์„œ๋Š” 127.0.0.1 IP๋กœ ์“ฐ๊ธฐ
  • [ios]์—์„œ ์•ˆ ๋  ๋•Œ๋Š” Podfile์— pod 'react-native-config', :path => '../node_modules/react-native-config/react-native-config.podspec' ์ถ”๊ฐ€ํ•ด๋ณด๊ธฐ

์•”ํ˜ธํ™”ํ•ด์„œ ์ €์žฅํ•  ๋ฐ์ดํ„ฐ๋Š” ๋‹ค์Œ ํŒจํ‚ค์ง€์—

import EncryptedStorage from 'react-native-encrypted-storage';
await EncryptedStorage.setItem('ํ‚ค', '๊ฐ’');
await EncryptedStorage.removeItem('ํ‚ค');
const ๊ฐ’ = await EncryptedStorage.getItem('ํ‚ค');
  • redux์— ๋„ฃ์€ ๋ฐ์ดํ„ฐ๋Š” ์•ฑ์„ ๋„๋ฉด ๋‚ ์•„๊ฐ
  • ์•ฑ์„ ๊บผ๋„ ์ €์žฅ๋˜์–ด์•ผ ํ•˜๊ณ  ๋ฏผ๊ฐํ•œ ๊ฐ’์€ encrypted-storage์—
  • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ฌ๋ผ์ง€๋Š” ๊ฐ’์€ react-native-config์— ์ €์žฅํ•˜๋ฉด ์ข‹์Œ(์•”ํ˜ธํ™” ์•ˆ ๋จ)
  • ๊ทธ ์™ธ์— ์œ ์ง€๋งŒ ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋“ค์€ async-storage์— ์ €์žฅ(npm install @react-native-async-storage/async-storage)

src/pages/SignUp.tsx, src/pages/SignIn.tsx

android์—์„œ http ์š”์ฒญ์ด ์•ˆ ๋ณด๋‚ด์ง€๋ฉด

  • android/app/src/main/AndroidManifest.xml ์—์„œ ํƒœ๊ทธ์— android:usesCleartextTraffic="true" ์ถ”๊ฐ€

ActivityIndicator๋กœ ๋กœ๋”ฉ์ฐฝ ๊พธ๋ฏธ๊ธฐ

์†Œ์ผ“IO ์—ฐ๊ฒฐ

์›น์†Œ์ผ“ ๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • ์š”์ฒญ-์‘๋‹ต ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹  ๊ฐ€๋Šฅ
npm i socket.io-client

src/hooks/useSocket.ts

import {useCallback} from 'react';
import {io, Socket} from 'socket.io-client';
import Config from 'react-native-config';

let socket: Socket | undefined;
const useSocket = (): [Socket | undefined, () => void] => {
  const disconnect = useCallback(() => {
    if (socket) {
      socket.disconnect();
      socket = undefined;
    }
  }, []);
  if (!socket) {
    socket = io(`${Config.API_URL}`, {
      transports: ['websocket'],
    });
  }
  return [socket, disconnect];
};

export default useSocket;

AppInner.tsx

  const [socket, disconnect] = useSocket();

  useEffect(() => {
    const helloCallback = (data: any) => {
      console.log(data);
    };
    if (socket && isLoggedIn) {
      console.log(socket);
      socket.emit('login', 'hello');
      socket.on('hello', helloCallback);
    }
    return () => {
      if (socket) {
        socket.off('hello', helloCallback);
      }
    };
  }, [isLoggedIn, socket]);

  useEffect(() => {
    if (!isLoggedIn) {
      console.log('!isLoggedIn', !isLoggedIn);
      disconnect();
    }
  }, [isLoggedIn, disconnect]);
  • login์„ emitํ•˜๋ฉด ๊ทธ๋•Œ๋ถ€ํ„ฐ ์„œ๋ฒ„๊ฐ€ hello๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์คŒ *๋กœ๊ทธ์•„์›ƒ ์‹œ์— disconnectํ•ด์ฃผ๋Š” ๊ฒƒ ์žŠ์ง€ ๋ง๊ธฐ

๋กœ๊ทธ์•„์›ƒ

src/pages/Settings.tsx


์‹ค์ œ ์ฃผ๋ฌธ ๋ฐ›๊ธฐ[ch3]

socket.io์—์„œ ์ฃผ๋ฌธ ๋‚ด์—ญ ๋ฐ›์•„์„œ store์— ๋„ฃ๊ธฐ

AppInner.tsx

  useEffect(() => {
    const callback = (data: any) => {
      console.log(data);
      dispatch(orderSlice.actions.addOrder(data));
    };
    if (socket && isLoggedIn) {
      socket.emit('acceptOrder', 'hello');
      socket.on('order', callback);
    }
    return () => {
      if (socket) {
        socket.off('order', callback);
      }
    };
  }, [isLoggedIn, socket]);

์•ฑ ๋‹ค์‹œ ์ผค ๋•Œ ์ž๋™๋กœ๊ทธ์ธ๋˜๊ฒŒ

encrypted-storage์—์„œ ํ† ํฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

AppInner.tsx

  // ์•ฑ ์‹คํ–‰ ์‹œ ํ† ํฐ ์žˆ์œผ๋ฉด ๋กœ๊ทธ์ธํ•˜๋Š” ์ฝ”๋“œ
  useEffect(() => {
    const getTokenAndRefresh = async () => {
      try {
        const token = await EncryptedStorage.getItem('refreshToken');
        if (!token) {
          return;
        }
        const response = await axios.post(
          `${Config.API_URL}/refreshToken`,
          {},
          {
            headers: {
              authorization: `Bearer ${token}`,
            },
          },
        );
        dispatch(
          userSlice.actions.setUser({
            name: response.data.data.name,
            email: response.data.data.email,
            accessToken: response.data.data.accessToken,
          }),
        );
      } catch (error) {
        console.error(error);
        if ((error as AxiosError).response?.data.code === 'expired') {
          Alert.alert('์•Œ๋ฆผ', '๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”.');
        }
      }
    };
    getTokenAndRefresh();
  }, [dispatch]);
  • ์ž ๊น ๋กœ๊ทธ์ธ ํ™”๋ฉด์ด ๋ณด์ด๋Š” ๊ฒƒ์€ SplashScreen์œผ๋กœ ์ˆจ๊น€

์ฃผ๋ฌธ ๋ฐ์ดํ„ฐ ๋ฆฌ๋•์Šค์— ์ €์žฅํ•˜๊ธฐ

src/slices/order.ts

์ˆ˜์ต๊ธˆ ํ™•์ธํ•˜๊ธฐ

src/pages/Settings.tsx


์ฃผ๋ฌธ ํ™”๋ฉด ๋งŒ๋“ค๊ธฐ(์ˆ˜๋ฝ/๊ฑฐ์ ˆ)

src/pages/Orders.tsx

  • ScrollView + map ์กฐํ•ฉ์€ ์ข‹์ง€ ์•Š์Œ
  • FlatList๋ฅผ ์“ฐ๊ธฐ
  • ๋ฐ˜๋ณต๋˜๋Š” ๊ฒƒ์€ ์ปดํฌ๋„ŒํŠธ๋กœ ๋นผ๋Š” ๊ฒƒ์ด ์ข‹์Œ
  • keyExtractor ๋ฐ˜๋“œ์‹œ ์„ค์ •ํ•˜๊ธฐ

src/components/EachOrder.tsx

accessToken ๋งŒ๋ฃŒ์‹œ ์ž๋™์œผ๋กœ refresh๋˜๊ฒŒ

axios.interceptor ์„ค์ •ํ•˜๊ธฐ

  useEffect(() => {
    axios.interceptors.response.use(
      response => {
        return response;
      },
      async error => {
        const {
          config,
          response: {status},
        } = error;
        if (status === 419) {
          if (error.response.data.code === 'expired') {
            const originalRequest = config;
            const refreshToken = await EncryptedStorage.getItem('refreshToken');
            // token refresh ์š”์ฒญ
            const {data} = await axios.post(
              `${Config.API_URL}/refreshToken`, // token refresh api
              {},
              {headers: {authorization: `Bearer ${refreshToken}`}},
            );
            // ์ƒˆ๋กœ์šด ํ† ํฐ ์ €์žฅ
            dispatch(userSlice.actions.setAccessToken(data.data.accessToken));
            originalRequest.headers.authorization = `Bearer ${data.data.accessToken}`;
            // 419๋กœ ์š”์ฒญ ์‹คํŒจํ–ˆ๋˜ ์š”์ฒญ ์ƒˆ๋กœ์šด ํ† ํฐ์œผ๋กœ ์žฌ์š”์ฒญ
            return axios(originalRequest);
          }
        }
        return Promise.reject(error);
      },
    );
  }, [dispatch]);

๋„ค์ด๋ฒ„ ์ง€๋„ ์‚ฌ์šฉํ•˜๊ธฐ[ch4]

npm i https://github.com/ZeroCho/react-native-naver-map

npm i react-native-nmap์„ ํ•˜๋ฉด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์•ˆ ๋˜๋Š” ํŒจํ‚ค์ง€๊ฐ€ ์„ค์น˜๋˜๋ฏ€๋กœ ๊ฐ•์˜๋ฅผ ์œ„ํ•ด ์ œ์ž‘๋œ ํŒจํ‚ค์ง€๋ฅผ ๋Œ€์‹  ์„ค์น˜

...
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )
  ...
  pod 'NMapsMap'
  ...
  target 'FoodDeliveryAppTests' do
...
npx pod-install # ios ์ „์šฉ

android/build.gradle

buildscript {
    ...
}
allprojects {
    repositories {
        google()
        jcenter()
        // ๋„ค์ด๋ฒ„ ์ง€๋„ ์ €์žฅ์†Œ
        maven {
            url 'https://naver.jfrog.io/artifactory/maven/'
        }
    }
}
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ ํŒจํ‚ค์ง€ ์ด๋ฆ„: com.[์›ํ•˜๋Š”์ด๋ฆ„].fooddeliveryapp (ex: com.zerocho.fooddeliveryapp)
  • ์ปค๋ฐ‹ ์ฐธ์กฐ (ํด๋” ๋“ฑ ๋ณ€๊ฒฝํ•  ๊ฒŒ ๋งŽ์Œ)
  • [ios]Xcode๋กœ๋Š” xcworkspace ํŒŒ์ผ์„ ์—ด์–ด์•ผํ•จ(xcodeproj ์—ด๋ฉด ์•ˆ๋จ, xcworkspace๊ฐ€ ์—†๋‹ค๋ฉด ios ํด๋”์—์„œ pod install ํ•œ ๋ฒˆ ์ž…๋ ฅํ•ด๋ณผ ๊ฒƒ)
  • [ios]iOS Bundle ID: com.[์›ํ•˜๋Š”์ด๋ฆ„].fooddeliveryapp(ex: com.zerocho.fooddeliveryapp)๋กœ ์ˆ˜์ •

src/components/EachOrder.tsx

<View
        style={{
          width: Dimensions.get('window').width - 30,
          height: 200,
          marginTop: 10,
        }}>
  <NaverMapView
          style={{width: '100%', height: '100%'}}
          zoomControl={false}
          center={{
            zoom: 10,
            tilt: 50,
            latitude: (start.latitude + end.latitude) / 2,
            longitude: (start.longitude + end.longitude) / 2,
          }}>
    <Marker
            coordinate={{
              latitude: start.latitude,
              longitude: start.longitude,
            }}
            pinColor="blue"
    />
    <Path
            coordinates={[
              {
                latitude: start.latitude,
                longitude: start.longitude,
              },
              {latitude: end.latitude, longitude: end.longitude},
            ]}
    />
    <Marker
            coordinate={{latitude: end.latitude, longitude: end.longitude}}
    />
  </NaverMapView>
</View>

์œ„์น˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

๊ถŒํ•œ ์–ป๊ธฐ(์œ„์น˜์ •๋ณด, ์นด๋ฉ”๋ผ, ๊ฐค๋Ÿฌ๋ฆฌ)

npm i react-native-permissions

ios/Podfile

pod 'NMapsMap', '3.16.0'
permissions_path = '../node_modules/react-native-permissions/ios'
pod 'Permission-Camera', :path => "#{permissions_path}/Camera"
pod 'Permission-LocationAccuracy', :path => "#{permissions_path}/LocationAccuracy"
pod 'Permission-LocationAlways', :path => "#{permissions_path}/LocationAlways"
pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
pod 'Permission-Notifications', :path => "#{permissions_path}/Notifications"
pod 'Permission-PhotoLibrary', :path => "#{permissions_path}/PhotoLibrary"

ios/FoodDeliveryApp/Info.plist

<key>NSCameraUsageDescription</key>
<string>๋ฐฐ์†ก์™„๋ฃŒ ์‚ฌ์ง„ ์ดฌ์˜์„ ์œ„ํ•ด ์นด๋ฉ”๋ผ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>๋ฐฐ์†ก์ค‘ ์œ„์น˜ ํ™•์ธ์„ ์œ„ํ•ด์„œ ์œ„์น˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>๋ฐฐ์†ก์ค‘ ์œ„์น˜ ํ™•์ธ์„ ์œ„ํ•ด์„œ ์œ„์น˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>๋ฐฐ์†ก์ค‘ ์œ„์น˜ ํ™•์ธ์„ ์œ„ํ•ด์„œ ์œ„์น˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSMotionUsageDescription</key>
<string>๋ฐฐ์†ก์ค‘ ์œ„์น˜ ํ™•์ธ์„ ์œ„ํ•ด์„œ ์œ„์น˜ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>๋ฐฐ์†ก์™„๋ฃŒ ์‚ฌ์ง„ ์„ ํƒ์„ ์œ„ํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ‘๊ทผ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>๋ฐฐ์†ก์™„๋ฃŒ ์‚ฌ์ง„ ์„ ํƒ์„ ์œ„ํ•ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ ‘๊ทผ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>

android/app/src/main/AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
npx pod-install

src/hooks/usePermissions.ts

import {useEffect} from 'react';
import {Alert, Linking, Platform} from 'react-native';
import {check, PERMISSIONS, request, RESULTS} from 'react-native-permissions';

function usePermissions() {
  // ๊ถŒํ•œ ๊ด€๋ จ
  useEffect(() => {
    if (Platform.OS === 'android') {
      check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION)
        .then(result => {
          console.log('check location', result);
          if (result === RESULTS.BLOCKED || result === RESULTS.DENIED) {
            Alert.alert(
              '์ด ์•ฑ์€ ์œ„์น˜ ๊ถŒํ•œ ํ—ˆ์šฉ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.',
              '์•ฑ ์„ค์ • ํ™”๋ฉด์„ ์—ด์–ด์„œ ํ•ญ์ƒ ํ—ˆ์šฉ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์„ธ์š”.',
              [
                {
                  text: '๋„ค',
                  onPress: () => Linking.openSettings(),
                },
                {
                  text: '์•„๋‹ˆ์˜ค',
                  onPress: () => console.log('No Pressed'),
                  style: 'cancel',
                },
              ],
            );
          }
        })
        .catch(console.error);
    } else if (Platform.OS === 'ios') {
      check(PERMISSIONS.IOS.LOCATION_ALWAYS)
        .then(result => {
          if (result === RESULTS.BLOCKED || result === RESULTS.DENIED) {
            Alert.alert(
              '์ด ์•ฑ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์œ„์น˜ ๊ถŒํ•œ ํ—ˆ์šฉ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.',
              '์•ฑ ์„ค์ • ํ™”๋ฉด์„ ์—ด์–ด์„œ ํ•ญ์ƒ ํ—ˆ์šฉ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ์„ธ์š”.',
              [
                {
                  text: '๋„ค',
                  onPress: () => Linking.openSettings(),
                },
                {
                  text: '์•„๋‹ˆ์˜ค',
                  onPress: () => console.log('No Pressed'),
                  style: 'cancel',
                },
              ],
            );
          }
        })
        .catch(console.error);
    }
    if (Platform.OS === 'android') {
      check(PERMISSIONS.ANDROID.CAMERA)
        .then(result => {
          if (result === RESULTS.DENIED || result === RESULTS.GRANTED) {
            return request(PERMISSIONS.ANDROID.CAMERA);
          } else {
            console.log(result);
            throw new Error('์นด๋ฉ”๋ผ ์ง€์› ์•ˆ ํ•จ');
          }
        })
        .catch(console.error);
    } else {
      check(PERMISSIONS.IOS.CAMERA)
        .then(result => {
          if (
            result === RESULTS.DENIED ||
            result === RESULTS.LIMITED ||
            result === RESULTS.GRANTED
          ) {
            return request(PERMISSIONS.IOS.CAMERA);
          } else {
            console.log(result);
            throw new Error('์นด๋ฉ”๋ผ ์ง€์› ์•ˆ ํ•จ');
          }
        })
        .catch(console.error);
    }
  }, []);
}

export default usePermissions;
  • [ios]ํ˜น์‹œ๋‚˜ ์•ฑ ์„ค์ • ํ™”๋ฉด์— ์œ„์น˜ ๊ถŒํ•œ์ด ์—†์„ ๊ฒฝ์šฐ Delivey ํŽ˜์ด์ง€๊นŒ์ง€ ํ•œ ๋ฒˆ ๋“ค์–ด๊ฐ”๋‹ค ๋‚˜์˜ค๊ธฐ. ๊ทธ๋Ÿผ ์ƒ๊ฒจ์žˆ์Œ.
  • Platform์œผ๋กœ ์šด์˜์ฒด์ œ ๊ตฌ๋ณ„
  • Linking์œผ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค ์—ด๊ธฐ ๊ฐ€๋Šฅ ์œ„์น˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
npm i @react-native-community/geolocation

src/pages/Ing.tsx

์ด๋ฏธ์ง€ ์„ ํƒํ•˜๊ธฐ(์ฃผ๋ฌธ ์™„๋ฃŒ)

src/pages/Complete.tsx

์ด๋ฏธ์ง€ ์„ ํƒ ํ›„ ๋ฆฌ์‚ฌ์ด์ง•

npm i react-native-image-crop-picker
npm i react-native-image-resizer
npx pod-install # ios ์ „์šฉ
  • ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ์—๋Š” multipart/form-data๋ฅผ ์‚ฌ์šฉํ•จ
  • ์ด๋ฏธ์ง€๋Š” { uri: ์ฃผ์†Œ, name: ํŒŒ์ผ๋ช…, type: ํ™•์žฅ์ž } ๊ผด
  • base64๋กœ ์ด๋ฏธ์ง€๋ฅผ ํ…์ŠคํŠธ๊ผด๋กœ ํ‘œํ˜„ ๊ฐ€๋Šฅ(์šฉ๋Ÿ‰ 33% ์ฆ๊ฐ€)
  • resizeMode: cover(๊ฝ‰ ์ฐจ๊ฒŒ), contain(๋”ฑ ๋งž๊ฒŒ), stretch(๋น„์œจ ๋ฌด์‹œํ•˜๊ณ  ๋”ฑ ๋งž๊ฒŒ), repeat(๋ฐ˜๋ณต๋˜๊ฒŒ), center(์ค‘์•™ ์ •๋ ฌ)

์‚ฌ์ง„ ์ฐ์„ ๋•Œ ์ด๋ฏธ์ง€๋ฅผ ์นด๋ฉ”๋ผ๋กค/๊ฐค๋Ÿฌ๋ฆฌ์— ์ €์žฅํ•˜๊ณ  ์‹ถ์Œ[ch5]

Native Module Patching

npm i patch-package

package.json

  "scripts": {
    "postinstall": "patch-package",
    "android": "react-native run-android",
  • patch ํ›„ ์ ์šฉํ•˜๊ธฐ
npx patch-package react-native-image-crop-picker
  • ์•ž์œผ๋กœ npm i ํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ํŒจ์น˜๊ฐ€ ์ ์šฉ๋จ(postinstall ์Šคํฌ๋ฆฝํŠธ ๋•๋ถ„)
  • ์ด๋Ÿฐ ๊ฒƒ ๋•Œ๋ฌธ์— ๋„ค์ดํ‹ฐ๋ธŒ๋ฅผ ์•Œ์•„์•ผํ•จ ใ… 

Tmap ์—ฐ๊ฒฐํ•˜๊ธฐ(Native Modules)

๊ฐ€์ž…

  • My Project - ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ - TMap API ์‹ ์ฒญ(๋ฌด๋ฃŒ)
  • sdk
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์—ฐ๋™
  • [ios]iOS ์—ฐ๋™
  • [ios]iOS ์—ฐ๋™์‹œ Header ํŒŒ์ผ๋“ค์ด project.pbxproj์— ๋“ฑ๋ก๋˜์—ˆ๋‚˜ ํ™•์ธ(๋‹ค๋ฅธ ๊ฒƒ๋„ ๋‹น์—ฐํžˆ)
  • android/app/src/java/com/zerocho/fooddeliveryapp/TMapModule.java ์ƒ์„ฑ
  • android/app/src/java/com/zerocho/fooddeliveryapp/TMapPackage.java ์ƒ์„ฑ
  • android/app/src/java/com/zerocho/fooddeliveryapp/MainApplication์— TMapPackage ์—ฐ๊ฒฐ
  • [ios]ios/FoodDeliveryApp/RCTTMap.h
  • [ios]ios/FoodDeliveryApp/RCTTMap.m
  • [ios]ios/FoodDeliveryApp-Bridging-Header.h
  • src/modules/TMap.ts

android/app/src/main/AndroidManifest.xml

...
  <queries>
    <package android:name="com.skt.tmap.ku" />
  </queries>
</manifest>

src/pages/Ing.tsx

TMap.openNavi(
  '๋„์ฐฉ์ง€',
  end.longitude.toString(),
  end.latitude.toString(),
  'MOTORCYCLE',
).then(data => {
  console.log('TMap callback', data);
  if (!data) {
    Alert.alert('์•Œ๋ฆผ', 'ํ‹ฐ๋งต์„ ์„ค์น˜ํ•˜์„ธ์š”.');
  }
});

react-native-splash-screen

npm i react-native-splash-screen
  • ์—ฌ๊ธฐ์„œ Third step๊ณผ Getting Started ๋”ฐ๋ผํ•˜๊ธฐ
  • android/app/src/main/res/drawable ํด๋” ๋งŒ๋“ค๊ณ  ๊ทธ ์•ˆ์— launch_screen.png ๋„ฃ๊ธฐ AppInner.tsx
...
        const token = await EncryptedStorage.getItem('refreshToken');
        if (!token) {
          SplashScreen.hide();
          return;
        }
        ...
      } finally {
        SplashScreen.hide();
      }
    };
    getTokenAndRefresh();
  }, [dispatch]);

์•ฑ icon ๋ณ€๊ฒฝ

  • Android ๋‹ค์šด๋ฐ›์€ ํ›„ android/app/src/main ์•„๋ž˜์— ๋„ฃ๊ธฐ
  • [ios] ๋งํฌ ์—์„œ ๋‹ค์šด๋กœ๋“œ๋œ Assets.xcassets๋ฅผ ios/FoodDeliveryApp ๋‚ด๋ถ€์— ๋„ฃ๊ธฐ
  • [ios]Xcode์—์„œ ์•„์ด์ฝ˜ ์—ฐ๊ฒฐ ํ•„์š”

์•ฑ ํ•˜๋‹จ ๋ฉ”๋‰ด ์•„์ด์ฝ˜

npm i react-native-vector-icons
npm i -D @types/react-native-vector-icons

๋ชฉ๋ก

  • android/app/src/main/assets/fonts์— node_modules/react-native-vector-icons/Fonts ํด๋” ๋ณต์‚ฌ
  • [ios]Xcode์—์„œ New Group์œผ๋กœ ๋ฉ”๋‰ด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  Fonts ๊ทธ๋ฃน์— node_modules/react-native-vector-icons/Fonts ํฐํŠธ๋“ค์„ ์ถ”๊ฐ€

์ฃผ๋ฌธ์™„๋ฃŒ ์‚ฌ์ง„๋“ค ๋ณด์—ฌ์ฃผ๊ธฐ

npm i react-native-fast-image

๋งํฌ src/slices/order.ts

interface InitialState {
  ...
  completes: Order[];
}
const initialState: InitialState = {
  ...
  completes: [],
};
...
    setCompletes(state, action) {
      state.completes = action.payload;
    },

src/pages/Settings.tsx

FCM

ํ‘ธ์‰ฌ์•Œ๋ฆผ ๋ณด๋‚ด๊ธฐ

npm i @react-native-firebase/analytics @react-native-firebase/app @react-native-firebase/messaging
npm i react-native-push-notification @react-native-community/push-notification-ios
npm i -D @types/react-native-push-notification
npx pod-install

[ios] pod install ์‹œ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ์ฐธ๊ณ 

[ios] ๋”ฐ๋ผํ•  ๊ฒƒ

  • firebase ํ”„๋กœ์ ํŠธ ์„ค์ • - Admin SDK - Node.js - ์ƒˆ ๋น„๊ณต๊ฐœํ‚ค ์ƒ์„ฑ - back ํด๋” ์•ˆ์— ๋„ฃ๊ณ  app.js ์†Œ์Šค ์ˆ˜์ •
  • ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ ์„ค์ • ํ›„ google-services.json์„ android/app์— ๋„ฃ๊ธฐ
  • [ios] ์•„์ดํฐ ์•ฑ ์„ค์ • ํ›„ ios/GoogleService-Info.plist ์ƒ์„ฑ
  • ๋ฐฐ์†ก ์™„๋ฃŒ์‹œ push ์•Œ๋ฆผ์ด ์˜ฌ ๊ฒƒ์ž„(์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ๋Š” ์•ˆ ์˜ฌ ์ˆ˜ ์žˆ์Œ)

๋ฆฌ์•กํŠธ ๋‚ด๋น„๊ฒŒ์ด์…˜๊ณผ ์—ฐ๋™

App.tsx

์‹ค๊ธฐ๊ธฐ ์‚ฌ์šฉํ•˜๊ธฐ[ch6]

๋งํฌ

  • samsung dex๊ฐ™์€ ๊ฑด ๋„๊ธฐ
  • ํ•ธ๋“œํฐ usb ์—ฐ๊ฒฐ ์‹œ usb ๋””๋ฒ„๊น… ํ—ˆ์šฉํ•˜๊ธฐ
  • .env์—์„œ ip์ฃผ์†Œ ๋ฐ”๊พธ๊ธฐ
adb devices
adb -s <๊ธฐ๊ธฐ์ด๋ฆ„> reverse tcp:8081 tcp:8081

์—ฌ๋Ÿฌ ๋ฌธ์ œ ๋ฐœ๊ฒฌ ๊ฐ€๋Šฅ

  • ํฐํŠธ๊ฐ€ ํฐ์ƒ‰: style์— color ์ฃผ๊ธฐ
  • vector-icons ์•ˆ ๋œธ: ์—ญ์‹œ style์— color ์ฃผ๊ธฐ(ch6 AppInner.tsx ์ฐธ๊ณ )

๋ฐฐํฌ ๊ด€๋ จ

Android

android/app/build.gradle

def enableSeparateBuildPerCPUArchitecture = true

/**
* Run Proguard to shrink the Java bytecode in release builds.
*/ 
def enableProguardInReleaseBuilds = true

package.json

  "scripts": {
    ...
    "build:android": "npm ci && cd android && ./gradlew bundleRelease && cd .. && open android/app/build/outputs/bundle/release",
    "apk:android": "npm ci && cd android && ./gradlew assembleRelease && cd .. && open android/app/build/outputs/apk/release",

์ถœ์‹œ ๊ณผ์ •

iOS

iOS ๊ฐœ๋ฐœ์ž ๋ฉค๋ฒ„์‰ฝ ๊ฐ€์ž… ํ•„์š”

์ถœ์‹œ ๊ณผ์ •

  • Xcode๋กœ Archive(์ด ๋•Œ simulator๋ฅผ ์„ ํƒํ•œ ์ƒํƒœ์ด๋ฉด ์•ˆ ๋จ)

[ios]fastlane

๋ฒ„์ €๋‹, ๋ฐฐํฌ ์ž๋™ํ™” ๊ฐ€๋Šฅ

CodePush

  • ์‹ค์‹œ๊ฐ„์œผ๋กœ ์•ฑ ์ˆ˜์ • ๊ฐ€๋Šฅ(JS์ฝ”๋“œ, ์ด๋ฏธ์ง€, ๋น„๋””์˜ค๋งŒ)
  • ๋…ธ๋“œ๋ชจ๋“ˆ, ๋„ค์ดํ‹ฐ๋ธŒ์ชฝ ์ˆ˜์ •์€ ์•ฑ ๋ฐฐํฌ ํ•„์š”

์•ฑ์„ผํ„ฐ ๊ฐ€์ž…

npm i react-native-code-push
npm install appcenter appcenter-analytics appcenter-crashes
npm i -g appcenter-cli (๋งฅ์—์„œ๋Š” sudo ํ•„์š”)
appcenter login
appcenter codepush deployment list -a zerohch0/food-delivery-app-android -k

App.tsx

import codePush from "react-native-code-push";

const codePushOptions: CodePushOptions = {
  checkFrequency: CodePush.CheckFrequency.MANUAL,
  // ์–ธ์ œ ์—…๋ฐ์ดํŠธ๋ฅผ ์ฒดํฌํ•˜๊ณ  ๋ฐ˜์˜ํ• ์ง€๋ฅผ ์ •ํ•œ๋‹ค.
  // ON_APP_RESUME์€ Background์—์„œ Foreground๋กœ ์˜ค๋Š” ๊ฒƒ์„ ์˜๋ฏธ
  // ON_APP_START์€ ์•ฑ์ด ์‹คํ–‰๋˜๋Š”(์ผœ์ง€๋Š”) ์ˆœ๊ฐ„์„ ์˜๋ฏธ
  installMode: CodePush.InstallMode.IMMEDIATE,
  mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
  // ์—…๋ฐ์ดํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ์„ค์น˜ํ•  ๊ฒƒ์ธ์ง€ (IMMEDIATE๋Š” ๊ฐ•์ œ์„ค์น˜๋ฅผ ์˜๋ฏธ)
};
function App() {
}

export default codePush(codePushOptions)(App);
"codepush:android": "appcenter codepush release-react -a ์•„์ด๋””/์•ฑ์ด๋ฆ„ -d ๋ฐฐํฌ์ด๋ฆ„ --sourcemap-output --output-dir ./build -m -t ํƒ€๊ฒŸ๋ฒ„์ „",
"codepush:ios": "appcenter codepush release-react -a ์•„์ด๋””/์•ฑ์ด๋ฆ„ -d ๋ฐฐํฌ์ด๋ฆ„ --sourcemap-output --output-dir ./build -m -t ํƒ€๊ฒŸ๋ฒ„์ „",
"bundle:android": "react-native bundle --assets-dest build/CodePush --bundle-output build/CodePush/index.android.bundle --dev false --entry-file index.js --platform android --sourcemap-output build/CodePush/index.android.bundle.map",
"bundle:ios": "react-native bundle --assets-dest build/CodePush --bundle-output build/CodePush/main.jsbundle --dev false --entry-file index.js --platform ios --sourcemap-output build/CodePush/main.jsbundle.map",
  • ์‹ค์ œ ์˜ˆ์‹œ๋Š” package.json ์ฐธ์กฐ

iOS Pod ๊ด€๋ จ

[๋งฅ ์ „์šฉ]ios ํด๋” ์•ˆ์—์„œ pod ๋ช…๋ น์–ด ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ, but npx pod-install์€ ํ”„๋กœ์ ํŠธ ํด๋” ์–ด๋””์„œ๋‚˜ ๊ฐ€๋Šฅ

  • Podfile: ์„ค์น˜ํ•  Pod๊ณผ ๊ฐœ๋ณ„์„ค์ •๋“ค ๊ธฐ๋ก
  • pod deintegrate: ๊ธฐ์กด pod๋“ค ์ œ๊ฑฐ
  • pod update: ๊ธฐ์กด pod ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ(pod install ์‹œ)
  • pod install: npx pod-install ์—ญํ•  Podfile.lock์— ๋”ฐ๋ผ ์„ค์น˜
  • pod install --repo-update: pod๋“ค ์„ค์น˜ํ•˜๋ฉด์„œ ์ตœ์‹ ์œผ๋กœ ์œ ์ง€

Hermes ์ผœ๊ธฐ

์‹œ์ž‘ ์„ฑ๋Šฅ ๋นจ๋ผ์ง€๊ณ , ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ ๊ณ , ์•ฑ ์‚ฌ์ด์ฆˆ ์ž‘์•„์ง

ํ—ค๋ฅด๋ฉ”์Šค ์ผœ๊ธฐ

๊ฟ€ํŒ๋“ค

  • patch-package: ๋…ธ๋“œ๋ชจ๋“ˆ์ฆˆ ์ง์ ‘ ์ˆ˜์ • ๊ฐ€๋Šฅ, ์œ ์ง€๋ณด์ˆ˜ ์•ˆ ๋˜๋Š” ํŒจํ‚ค์ง€ ์—…๋ฐ์ดํŠธ ์‹œ ์œ ์šฉ, ๋‹ค๋งŒ patch-packageํ•œ ํŒจํ‚ค์ง€๋Š” ์ถ”ํ›„ ๋ฒ„์ „ ์•ˆ ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ์ข‹์Œ
  • Sentry: ๋ฐฐํฌ ์‹œ React Native์šฉ์œผ๋กœ ๋ถ™์—ฌ์„œ ์—๋Ÿฌ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋ฉด ์ข‹์Œ(๋ฌด๋ฃŒ ์ง€์›)
  • react-native-upgrade helper: ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ ๋ฐฉ๋ฒ• ๋‚˜์˜ด

์—๋Ÿฌ๋“ค

Error: listen EADDRINUSE: address already in use :::8081

์ด๋ฏธ ๋ฉ”ํŠธ๋กœ ์„œ๋ฒ„๊ฐ€ ๋‹ค๋ฅธ ๋ฐ์„œ ์ผœ์ ธ ์žˆ๋Š” ๊ฒƒ์ž„. ๋ฉ”ํŠธ๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋Š” ํ„ฐ๋ฏธ๋„ ์ข…๋ฃŒํ•˜๊ธฐ

npm run android ์‹œ Running jetifier to migrate libraries to AndroidX.์ชฝ์—์„œ ์•ˆ ๋„˜์–ด๊ฐ€๋Š” ๊ฒฝ์šฐ

๋ฉ”ํŠธ๋กœ ์„œ๋ฒ„ ๊บผ๋ณผ ๊ฒƒ

์™„๋ฃŒ์ฒ˜๋ฆฌ ์‹œ "์œ ํšจํ•˜์ง€ ์•Š์€ ์ฃผ๋ฌธ์ž…๋‹ˆ๋‹ค."

[email protected] ์„ค์น˜([email protected]์— ๋ฌธ์ œ ์žˆ์Œ) ๋งํฌ

java.lang.RuntimeException: Unable to load script. Make sure you're either running Metro (run 'npx react-native start') or that your bundle 'index.android.bundle' is packaged correctly for release.

  • android/app/src/main/assets ํด๋” ๋งŒ๋“ค๊ธฐ
cd android
./gradlew clean
cd ..
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle

Execution failed for task ':app:packageDebug'. > java.lang.OutOfMemoryError (no error message)

android/gradle.properties์— ๋‹ค์Œ ์ค„ ์ถ”๊ฐ€

org.gradle.jvmargs=-XX\:MaxHeapSize\=1024m -Xmx1024m

๋˜๋Š”

android/app/src/main/AndroidManifest.xml ์—์„œ ํƒœ๊ทธ์— android:largeHeap="true" ์ถ”๊ฐ€

warn No apps connected. Sending "reload" to all React Native apps failed. Make sure your app is running in the simulator or on a phone connected via USB.

npx react-native start --reset-cache
cd android && ./gradlew clean
cd ..
npx react-native run-android

ERR_OSSL_DSO_COULD_NOT_LOAD_THE_SHARED_LIBRARY

์œˆ๋„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ธ๋ฐ choco๋กœ openssl ๋‹ค์‹œ ์„ค์น˜ํ•˜๊ธฐ

Error: spawn ./gradlew EACCES

chmod 755 android/gradlew

error: bundling failed: TypeError: Cannot read property 'transformFile' of undefined

node.js 16๋ฒ„์ „์œผ๋กœ ํ•  ๊ฒƒ, node 17๋ฒ„์ „๋ถ€ํ„ฐ ํ•ด๋‹น ์—๋Ÿฌ ๋ฐœ์ƒํ•จ.

ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)

๋ณดํ†ต App.tsx ๋ถ€๋ถ„์ด ์—ฌ๋Ÿฌ๋ฒˆ ์‹คํ–‰๋˜์–ด์„œ ๋ฐœ์ƒํ•จ. Metro ์„œ๋ฒ„๋ฅผ ๊ป๋‹ค๊ฐ€ ์ผœ๊ณ , ์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ์•ฑ์„ ์ง€์› ๋‹ค๊ฐ€ ๋‹ค์‹œ ์„ค์น˜ํ•˜๋ฉด ํ•ด๊ฒฐ ๋จ

Manifest merger failed : android:exported needs to be explicitly specified for element <receiver#com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver>. Apps targeting Android 12 and higher are required to specify an explicit value for android:exported when the corresponding component has an intent filter defined

๋งํฌ

์Šค์Šค๋กœ ํ•ด๋ณด๋ฉด ์ข‹์„ ๊ฒƒ

  • loading, disabled ์ฒ˜๋ฆฌ ๋ชจ๋‘ ๋‹ค ํ•˜๊ธฐ
  • ๋‚ด ์œ„์น˜ ์•ฑ ์‹œ์ž‘ํ•˜๊ณ  ๊ถŒํ•œ ์žˆ์„ ๋•Œ ๋ฏธ๋ฆฌ ๋ฐ›์•„๋†“๊ธฐ
  • refreshtoken์ด ๋งŒ๋ฃŒ๋˜๋ฉด ์–ด๋–ป๊ฒŒ?(ํ˜„์žฌ๋Š” ๋ฌดํ•œ 419๋œธ)

More Repositories

1

nodejs-book

Node.js๊ต๊ณผ์„œ ์†Œ์Šค ์ฝ”๋“œ
JavaScript
771
star
2

react-nodebird

JavaScript
328
star
3

sleact

TypeScript
304
star
4

ts-all-in-one

TypeScript
277
star
5

react-webgame

JavaScript
275
star
6

html-css-naver

HTML
183
star
7

webgame-lecture

JavaScript
104
star
8

es2021-webgame

HTML
100
star
9

nodejs-crawler

JavaScript
97
star
10

next-app-router-z

TypeScript
93
star
11

vue-webgame

Vue
63
star
12

vue-nodebird

JavaScript
52
star
13

ts-nodebird

HTML
42
star
14

redux-vs-mobx

JavaScript
37
star
15

ts-react

TypeScript
35
star
16

react-filepicker

react component for filepicker
JavaScript
29
star
17

ts-book

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ต๊ณผ์„œ ์†Œ์Šค ์ฝ”๋“œ
TypeScript
29
star
18

cs-database

26
star
19

react-vote

JavaScript
22
star
20

ts-lecture

TypeScript
21
star
21

cs-network-http

JavaScript
18
star
22

react-fb-like

React component for facebook like button!
JavaScript
17
star
23

cs-datastructure

์ž๋ฃŒ๊ตฌ์กฐ ๊ฐ•์ขŒ ์†Œ์Šค ์ฝ”๋“œ
JavaScript
17
star
24

react-nodebird-next13

TypeScript
16
star
25

z-com

TypeScript
15
star
26

es2020-webgame

JavaScript
8
star
27

next.js

JavaScript
6
star
28

nest-prisma

TypeScript
6
star
29

aws-upload

JavaScript
6
star
30

zerocho-sw

HTML
6
star
31

nodebird

JavaScript
5
star
32

whenwemeet

Application that graphically shows available time people can meet
JavaScript
5
star
33

zplanner

offline webapp planner that syncs
JavaScript
4
star
34

nodejs-gcloud

Dockerfile
4
star
35

google-image-crawler

JavaScript
4
star
36

kakao-sdk

Kakao javascript SDK packaged for Meteor
JavaScript
3
star
37

gitExample

HTML
3
star
38

kumovie

Java
3
star
39

node-window

JavaScript
3
star
40

free-git

2
star
41

food-delivery-app-69

Java
2
star
42

daum-maps-api

Daum Maps API async packaged for Meteor
JavaScript
2
star
43

test

2
star
44

es2024-webgame

HTML
1
star
45

naver-html-css-new

HTML
1
star
46

html-css-naver-new

HTML
1
star