[RN] ๐Ÿ“ฑReact Native ์‚ฌ์šฉํ•˜๊ธฐ

TATAยท2023๋…„ 7์›” 25์ผ
0

React-Native

๋ชฉ๋ก ๋ณด๊ธฐ
2/7

โ–ท React Native

React Native๋Š” JavaScript์™€ React๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ iOS์™€ Android ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

๐Ÿ“ฑ Expo๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

# ์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ์„ค์น˜
# npm install -g expo-cli

npx create-expo-app --template

# ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ
# npx create-expo-app -t expo-template-blank-typescript

๐Ÿ“ฑ CLI๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

Mac์—์„œ CLI๋กœ React-Native ์‹œ์ž‘ํ•˜๊ธฐ


๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์—๋ฎฌ๋ ˆ์ด์…˜

โˆ’ Android Studio a
โˆ’ Xcode i
xcode ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ


โ–ท Core Components

Core Components โ†’ React Native์˜ ๊ธฐ๋ณธ์ ์ธ UI ์š”์†Œ๋“ค

SafeAreaView: ํ™”๋ฉด ์ƒ๋‹จ์ด๋‚˜ ํ•˜๋‹จ์˜ ํ™ˆ ๋ฐ” ๋“ฑ๊ณผ ๊ฐ™์€ ์š”์†Œ๋กœ๋ถ€ํ„ฐ ์ปจํ…์ธ ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์คŒ. (OS ๋ฒ„์ „ 11 ์ด์ƒ์ด ์„ค์น˜๋œ iOS ๊ธฐ๊ธฐ์—๋งŒ ์ ์šฉ๋จ)

StatusBar: ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค์˜ ์ƒํƒœ ํ‘œ์‹œ์ค„์— ๋Œ€ํ•œ ์„ค์ •์„ ์ œ์–ด ๊ฐ€๋Šฅ.

<StatusBar backgroundColor="white" barStyle="dark-content" />

TextIput: ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ์„ ๋ฐ›์Œ. (๋น„๋ฐ€๋ฒˆํ˜ธ์ผ ๊ฒฝ์šฐ secureTextEntry ์†์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ)

FlatList: ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์ž„.

/* FlatList ์ปดํฌ๋„ŒํŠธ์˜ ์˜ต์…˜ */
// data => ๋ Œ๋”๋งํ•  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์„ ์„ค์ •
// renderItem => ๊ฐ ์•„์ดํ…œ์„ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์„ค์ •
// keyExtractor => ๊ณ ์œ ํ•œ ํ‚ค ๊ฐ’

const MyComponent = () => {
  // ๊ฐ€์ƒ์˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด
  const data = [
    { id: '1', title: 'Item 1' },
    { id: '2', title: 'Item 2' },
    { id: '3', title: 'Item 3' },
    // ์ถ”๊ฐ€์ ์ธ ์•„์ดํ…œ๋“ค...
  ];

  return (
    <FlatList
      data={data} // ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด
      renderItem={({ item }) => <ItemComponent {...item} />} // ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์•„์ดํ…œ์„ ๋ Œ๋”๋ง
      keyExtractor={(item) => item.id} // ๊ฐ ์•„์ดํ…œ์˜ ๊ณ ์œ  ํ‚ค๋ฅผ ์ง€์ •
    />
  );
};

KeyboardAvoidingView: ํ‚ค๋ณด๋“œ๊ฐ€ ํ™”๋ฉด์„ ๊ฐ€๋ฆฌ๋Š” ์ƒํ™ฉ์—์„œ ์ž๋™์œผ๋กœ ํ™”๋ฉด์„ ์กฐ์ •ํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ๋„์™€์ฃผ๋Š” ์—ญํ• ์„ ํ•จ.

import { KeyboardAvoidingView } from 'react-native';

...
<KeyboardAvoidingView
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
</KeyboardAvoidingView>

Pressable: ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ํ„ฐ์น˜ ์ž…๋ ฅ์— ๋ฐ˜์‘ํ•˜๋Š” ์—ญํ• ์„ ํ•จ.

<Pressable onPress={onPressFunction}>
  <Text>I'm pressable!</Text>
</Pressable>

-----
<Pressable
  hitSlop={20} // hitSlop => ์‚ฌ์šฉ์ž๊ฐ€ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์˜์—ญ์„ ํ™•์žฅํ•˜๋Š” ์—ญํ• 
  pressRetentionOffset={30} // pressRetentionOffset => ์‚ฌ์šฉ์ž๊ฐ€ ํ„ฐ์น˜๋ฅผ ์‹œ์ž‘ํ•œ ํ›„์—๋„ ํ•ด๋‹น ์˜์—ญ์„ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ์—๋งŒ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋Š” ์—ญํ• 
> 
  <Text>I'm pressable!</Text>
</Pressable>

TouchableOpacity: ํ„ฐ์น˜ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ํˆฌ๋ช…๋„๊ฐ€ ๋ณ€ํ•จ. (ํ„ฐ์น˜ ํšจ๊ณผ๋ฅผ ์ฃผ๋Š” ๋ฐ ์ฃผ๋กœ ์‚ฌ์šฉ)


โ–ท ์ปดํฌ๋„ŒํŠธ์˜ ์ด๋ฒคํŠธ ์†์„ฑ

TextInput ์ปดํฌ๋„ŒํŠธ
onChangeText: TextInput์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
onSubmitEditing: TextInput์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ œ์ถœํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ

Pressable ์ปดํฌ๋„ŒํŠธ
onPress: Pressable๋ฅผ ํƒญํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ

TouchableOpacity ์ปดํฌ๋„ŒํŠธ
onPress: TouchableOpacity๋ฅผ ํƒญํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ

ScrollView ์ปดํฌ๋„ŒํŠธ
onScroll: ScrollView๊ฐ€ ์Šคํฌ๋กค๋˜๋Š” ๋™์•ˆ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
onContentSizeChange: ScrollView์˜ ๋‚ด์šฉ ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ

FlatList ์ปดํฌ๋„ŒํŠธ
onEndReached: ์Šคํฌ๋กคํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ์˜ ๋์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
onRefresh: ๋ฆฌ์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจ ํ•˜๋„๋ก ์š”์ฒญํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ

Image ์ปดํฌ๋„ŒํŠธ
onLoad: ์ด๋ฏธ์ง€ ๋กœ๋“œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
onError: ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ


โ–ท StyleSheet

StyleSheet

// StyleSheet ๊ฐ€์ ธ์˜ค๊ธฐ
import { StyleSheet, Text, View } from 'react-native';

const LotsOfStyles = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.red}>just red</Text>
      <Text style={styles.bigBlue}>just bigBlue</Text>
      <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text>
      <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === "android" ? 20 : 0,
  },
  bigBlue: {
    color: 'blue',
    fontWeight: 'bold', // ์นด๋ฉœ์ผ€์ด์Šค ์‚ฌ์šฉ
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});

export default LotsOfStyles;

โœš ์ฐธ๊ณ 

styled-components๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

# ์„ค์น˜
npm install --save styled-components

# ํƒ€์ž…์Šคํฌํฌ๋ฆฝํŠธ ์‚ฌ์šฉ ์‹œ ์ถ”๊ฐ€ ์„ค์น˜
# npm install -D @types/styled-components @types/styled-components-react-native
/* styled-components ์‚ฌ์šฉ ์˜ˆ์‹œ */
import { styled } from 'styled-components';

const StyledTextInput = styled.TextInput`
  background-color: pink;
`;

...
<StyledTextInput placeholder="์ด๋ฉ”์ผ" value={email} onChangeText={setEmail}/>

โ–ท svg ์‚ฌ์šฉ๋ฒ•

# ์„ค์น˜
npm install react-native-svg
# Expo์ผ ๊ฒฝ์šฐ โ†“
# expo install react-native-svg

npm install -D react-native-svg-transformer

metro.config.js ํŒŒ์ผ ์ƒ์„ฑ

/* metro.config.js */
const { getDefaultConfig } = require("expo/metro-config");

module.exports = (() => {
  const config = getDefaultConfig(__dirname);

  const { transformer, resolver } = config;

  config.transformer = {
    ...transformer,
    babelTransformerPath: require.resolve("react-native-svg-transformer"),
  };
  config.resolver = {
    ...resolver,
    assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
    sourceExts: [...resolver.sourceExts, "svg"],
  };

  return config;
})();

โ–ท React Navigation

React Navigation โ†’ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

์„ค์น˜

npm install @react-navigation/native @react-navigation/native-stack
@react-navigation/bottom-tabs @react-navigation/material-top-tabs --save

npm install react-native-screens react-native-safe-area-context
# expo์ผ ๊ฒฝ์šฐ
# npx expo install react-native-screens react-native-safe-area-context

๐Ÿ“ฑ NavigationContainer, createNativeStackNavigator

/* App.js */
// NavigationContainer, createNativeStackNavigator ๊ฐ€์ ธ์˜ค๊ธฐ
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();
// ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ
// const navigation = useNavigation<NativeStackNavigationProp<any>>();

const MyStack = () => {
  return (
    <NavigationContainer> // ๊ฐ์‹ธ๊ธฐ
      <Stack.Navigator> // ๊ฐ์‹ธ๊ธฐ2
        <Stack.Screen // ์ปดํฌ๋„ŒํŠธ ๊ฒฝ๋กœ ์ง€์ •
          name="Home"
          component={HomeScreen} 
          options={{ headerShown: false }} // ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€ ์•ˆ ๋ณด์ด๊ฒŒ ํ•ด์คŒ
          // options={{ title: 'Welcome' }} => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€ ๋ณ€๊ฒฝ
        />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator> // ๊ฐ์‹ธ๊ธฐ2
    </NavigationContainer> // ๊ฐ์‹ธ๊ธฐ
  );
};

๐Ÿ“ฑ Naviagation Props

function Home({ navigation }) {
  const goToProfile = () => {
    navigation.navigate('Profile'); // Profile๋กœ ๊ฒฝ๋กœ ์ด๋™.
    // navigation.goBack() => ๋’ค๋กœ๊ฐ€๊ธฐ
    // navigation.push('Profile') => ์ƒˆ ํ™”๋ฉด์„ ์Šคํƒ์— ๋„ฃ์Œ
    // navigation.replace('Profile') => ํ˜„์žฌ ํ™”๋ฉด์„ ์ƒˆ ํ™”๋ฉด์œผ๋กœ ๊ต์ฒด
    // navigation.reset({ index: 0, routes: [{ name: 'Profile' }] }) => routes ๋ฐฐ์—ด๋กœ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉฐ, index๊ฐ€ 0์ธ Profile๋กœ ์ด๋™
    // navigation.setOptions({title: 'Notice'}) => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€์„ Notice๋กœ ๋ณ€๊ฒฝ
  };
  
  return (
    <Pressable onPress={goToProfile}>
      <Text>Go to Profile</Text>
    </Pressable>
  );
}

๐Ÿ“ฑ Params

Params ๋„˜๊ธฐ๊ธฐ

/* Home.jsx */
function Home({ navigation }) {
  const goToProfile = () => {
    navigation.navigate('Profile', { // ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•จ
      userId: 1,
      nickname: 'tata',
    });
  };
  
  return (
    <Pressable onPress={goToProfile}>
      <Text>Go to Profile</Text>
    </Pressable>
  );
}

Params ์ „๋‹ฌ๋ฐ›๊ธฐ

/* Profile.jsx */
function Profile({ navigation, route }) {
  const { userId, nickname } = route.params;

  return (
    <View>
      <Text>Profile</Text>
      <Text>{userId}</Text>
      <Text>{nickname}</Text>
    </View>
  );
}

... + map ์‚ฌ์šฉ

function Home({ navigation }) {
  const profiles = [
    { userId: 1, nickname: 'tata' },
    { userId: 2, nickname: 'coco' },
    { userId: 3, nickname: 'momo' },
  ];

  const goToProfile = (userId) => {
    navigation.navigate('Profile', {
      userId,
    });
  };

  return (
    <View>
      {profiles.map((profile) => (
        <Pressable key={profile.userId} onPress={() => goToProfile(profile.userId)}>
          <Text>{profile.nickname}</Text>
        </Pressable>
      ))}
    </View>
  );
}

๐Ÿ“ฑ useNavigation

import { useNavigation } from '@react-navigation/native';

function Home() {
  const navigation = useNavigation();
  
  const goToProfile = () => {
    navigation.navigate('Profile'); // Profile๋กœ ๊ฒฝ๋กœ ์ด๋™.
    // navigation.goBack() => ๋’ค๋กœ๊ฐ€๊ธฐ
    // navigation.push('Profile') => ์ƒˆ ํ™”๋ฉด์„ ์Šคํƒ์— ๋„ฃ์Œ
    // navigation.replace('Profile') => ํ˜„์žฌ ํ™”๋ฉด์„ ์ƒˆ ํ™”๋ฉด์œผ๋กœ ๊ต์ฒด
    // navigation.reset({ index: 0, routes: [{ name: 'Profile' }] }) => routes ๋ฐฐ์—ด๋กœ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉฐ, index๊ฐ€ 0์ธ Profile๋กœ ์ด๋™
    // navigation.setOptions({title: 'Notice'}) => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€์„ Notice๋กœ ๋ณ€๊ฒฝ
  };
  
  return (
    <Pressable onPress={goToProfile}>
      <Text>Go to Profile</Text>
    </Pressable>
  );

โ–ท Alert, Toast

๐Ÿ“ฑ Alert

try {
 ...
} catch (error) {
  Alert.alert(
    '์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.',
    error.message,
    [{ text: '๋‹ซ๊ธฐ', onPress: () => console.log('๋‹ซ๊ธฐ') }],
    { cancelable: true } // alert์ฐฝ์˜ ๋ฐ–์„ ๋ˆŒ๋Ÿฌ๋„ alert์ฐฝ์„ ๋‹ซ๊ฒŒ ํ•ด์คŒ
  );
}

๐Ÿ“ฑ Toast

# ์„ค์น˜
npm install --save react-native-toast-message

App.js

/* App.js */
import Toast from 'react-native-toast-message';

export function App(props) {
  return (
    <>
      {/* ... */}
      <Toast />
    </>
  );

components/SignupButton.jsx

/* SignupButton.jsx */
import Toast from 'react-native-toast-message';

...
Toast.show({
  type: 'success',
  text1: 'ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ',
  text2: `${email}์œผ๋กœ ๊ฐ€์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`,
});



๐Ÿ‘‰ AsyncStorage ์‚ฌ์šฉ๋ฒ•

profile
๐Ÿพ

0๊ฐœ์˜ ๋Œ“๊ธ€