์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ ๋งŒ๋“ค๊ธฐ: React Native ํƒ€์ž… ์‹œ์Šคํ…œ ์™„์ „ ์ •๋ณต

๐Ÿš€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ ๋งŒ๋“ค๊ธฐ: React Native ํƒ€์ž… ์‹œ์Šคํ…œ ์™„์ „ ์ •๋ณต

์นœ๊ตฌ์•ผ, ์ด์ œ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฒ• ์•Œ๋ ค์ค„๊ฒŒ! ๐ŸŽฏ

TS TypeScript React Native Mobile App

๐ŸŽฌ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์—: ์™œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ์„ ๋งŒ๋“ค๊นŒ?

์•ผ, ์†”์งํžˆ ๋งํ•ด์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ๋งŒ React Native ๊ฐœ๋ฐœํ•˜๋‹ค๊ฐ€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋„์ž…ํ•˜๋ฉด ์ฒ˜์Œ์—” ์ข€ ๊ท€์ฐฎ์•„. ๐Ÿ˜… ๊ทผ๋ฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๋ฉด์„œ "์•„, ์ด๊ฑฐ ์ง„์งœ ํ•„์š”ํ–ˆ๊ตฌ๋‚˜" ์‹ถ์€ ์ˆœ๊ฐ„์ด ์˜จ๋‹ค๋‹ˆ๊นŒ?

๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ๊ฒช์€ ์ผ์ธ๋ฐ, ์–ด๋А ๋‚  API ์‘๋‹ต ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์–ด. ๊ทธ๋Ÿฐ๋ฐ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ๋งŒ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์—์„œ๋Š” ์•ฑ์„ ์‹คํ–‰ํ•ด๋ด์•ผ๋งŒ ์—๋Ÿฌ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ์ง€. ๊ทผ๋ฐ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์“ฐ๋ฉด? ์ฝ”๋“œ ์ž‘์„ฑํ•˜๋Š” ์ˆœ๊ฐ„ ๋ฐ”๋กœ ๋นจ๊ฐ„ ์ค„์ด ์ซ™ ๊ทธ์–ด์ง€๋ฉด์„œ "์•ผ, ์—ฌ๊ธฐ ํƒ€์ž… ์•ˆ ๋งž์•„!" ํ•˜๊ณ  ์•Œ๋ ค์ค˜. ์ด๊ฒŒ ์–ผ๋งˆ๋‚˜ ๊ณ ๋งˆ์šด์ง€ ๋ชฐ๋ผ! ๐ŸŽฏ

๐Ÿ’ก ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ + React Native์˜ ํ•ต์‹ฌ ์žฅ์ 

ํƒ€์ž… ์•ˆ์ „์„ฑ: ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฏธ๋ฆฌ ์žก์•„๋ƒ„
์ž๋™์™„์„ฑ: IDE์—์„œ ์ฝ”๋“œ ์ž‘์„ฑ์ด ํ›จ์”ฌ ํŽธํ•ด์ง
๋ฆฌํŒฉํ† ๋ง: ์ฝ”๋“œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ ์‹œ ์˜ํ–ฅ๋ฐ›๋Š” ๋ถ€๋ถ„์„ ์ฆ‰์‹œ ํŒŒ์•…
ํ˜‘์—…: ํŒ€์›๋“ค์ด ์ฝ”๋“œ ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ์ดํ•ด ๊ฐ€๋Šฅ
๋ฌธ์„œํ™”: ํƒ€์ž… ์ž์ฒด๊ฐ€ ์‚ด์•„์žˆ๋Š” ๋ฌธ์„œ ์—ญํ• 

ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ, ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ "ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๊ฐœ๋ฐœํ•ฉ๋‹ˆ๋‹ค"๋ผ๊ณ  ํ•˜๋ฉด ์‹ ๋ขฐ๋„๊ฐ€ ํ™• ์˜ฌ๋ผ๊ฐ€๋”๋ผ๊ณ . ์™œ๋ƒ๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋ณด์žฅ๋˜๋‹ˆ๊นŒ! ๐Ÿ’ช

๐Ÿ› ๏ธ React Native ํ”„๋กœ์ ํŠธ์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์„ธํŒ…ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๋ถ€ํ„ฐ ์•Œ๋ ค์ค„๊ฒŒ.

๐Ÿ“ฆ ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

์š”์ฆ˜์€ React Native CLI๊ฐ€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ง€์›ํ•ด์„œ ์ง„์งœ ์‰ฌ์›Œ์กŒ์–ด:

npx react-native init MyAwesomeApp --template react-native-template-typescript

์ด ๋ช…๋ น์–ด ํ•˜๋‚˜๋ฉด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์™„์ „ํžˆ ์„ค์ •๋œ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ฟ…! ํ•˜๊ณ  ์ƒ์„ฑ๋ผ. ๐Ÿ˜Ž

๐Ÿ”ฅ ๊ฟ€ํŒ!
๋งŒ์•ฝ ๊ธฐ์กด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋ ค๋ฉด? ์ ์ง„์ ์œผ๋กœ ํ•˜๋Š” ๊ฒŒ ์ •์‹ ๊ฑด๊ฐ•์— ์ข‹์•„. .js ํŒŒ์ผ์„ .tsx๋กœ ํ•˜๋‚˜์”ฉ ๋ฐ”๊ฟ”๊ฐ€๋ฉด์„œ ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ผ!

โš™๏ธ tsconfig.json ์„ค์ • ํŒŒํ—ค์น˜๊ธฐ

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— ์žˆ๋Š” tsconfig.json ํŒŒ์ผ์ด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ๋‘๋‡Œ์•ผ. ๋‚ด๊ฐ€ ์‹ค์ „์—์„œ ์“ฐ๋Š” ์„ค์ •์„ ๊ณต์œ ํ•ด์ค„๊ฒŒ:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "lib": ["es2017"],
    "jsx": "react-native",
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  },
  "exclude": [
    "node_modules",
    "babel.config.js",
    "metro.config.js"
  ]
}

๐ŸŽฏ ํ•ต์‹ฌ ์˜ต์…˜ ์„ค๋ช…

์˜ต์…˜ ์˜๋ฏธ ์™œ ์ค‘์š”ํ•œ๊ฐ€?
strict ๋ชจ๋“  ์—„๊ฒฉํ•œ ํƒ€์ž… ์ฒดํฌ ํ™œ์„ฑํ™” ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ์ตœ๋Œ€ํ•œ ๋ฐฉ์ง€
jsx JSX ๋ฌธ๋ฒ• ์ฒ˜๋ฆฌ ๋ฐฉ์‹ React Native ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ ํ•„์ˆ˜
noImplicitAny ์•”๋ฌต์  any ํƒ€์ž… ๊ธˆ์ง€ ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ž‘์„ฑํ•˜๊ฒŒ ๊ฐ•์ œ
strictNullChecks null/undefined ์—„๊ฒฉ ์ฒดํฌ null ์ฐธ์กฐ ์—๋Ÿฌ ๋ฐฉ์ง€
esModuleInterop CommonJS ๋ชจ๋“ˆ ํ˜ธํ™˜์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ import ๋ฌธ์ œ ํ•ด๊ฒฐ
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ
strict: true๋ฅผ ์ผœ๋ฉด ์ฒ˜์Œ์—” ์—๋Ÿฌ๊ฐ€ ์—„์ฒญ ๋งŽ์ด ๋‚˜์˜ฌ ๊ฑฐ์•ผ. ๊ทผ๋ฐ ์ด๊ฒŒ ์ •์ƒ์ด์•ผ! ํ•˜๋‚˜์”ฉ ํ•ด๊ฒฐํ•˜๋‹ค ๋ณด๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ์ด ํ™• ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฑธ ๋А๋‚„ ์ˆ˜ ์žˆ์–ด.
React Native ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ์‹œ์Šคํ…œ Props interface Props type Props FC<Props> State useState<T> useReducer Context API Events onPress onChange Gesture

๐ŸŽจ React Native ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ์ •์˜ํ•˜๊ธฐ

์ž, ์ด์ œ ์ง„์งœ ์žฌ๋ฏธ์žˆ๋Š” ๋ถ€๋ถ„์ด์•ผ! React Native ์ปดํฌ๋„ŒํŠธ์— ํƒ€์ž…์„ ์–ด๋–ป๊ฒŒ ์ž…ํžˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž. ๐ŸŽญ

๐Ÿงฉ ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ Props ํƒ€์ž… ์ •์˜

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ. ์‹ค์ „์—์„œ ์ •๋ง ๋งŽ์ด ์“ฐ๋Š” ํŒจํ„ด์ด์•ผ:

import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

// Props ํƒ€์ž… ์ •์˜
interface CustomButtonProps {
  title: string;
  onPress: () => void;
  disabled?: boolean;
  backgroundColor?: string;
  textColor?: string;
}

// ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ with ํƒ€์ž…
const CustomButton: React.FC<CustomButtonProps> = ({
  title,
  onPress,
  disabled = false,
  backgroundColor = '#007AFF',
  textColor = '#FFFFFF',
}) => {
  return (
    <TouchableOpacity
      style={[
        styles.button,
        { backgroundColor },
        disabled && styles.disabled,
      ]}
      onPress={onPress}
      disabled={disabled}
    >
      <Text style={[styles.text, { color: textColor }]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  button: {
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  },
  disabled: {
    opacity: 0.5,
  },
  text: {
    fontSize: 16,
    fontWeight: '600',
  },
});

export default CustomButton;

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ํฌ์ธํŠธ๋“ค์ด ์žˆ์–ด! ๐ŸŽฏ

๐Ÿ“Œ Props ํƒ€์ž… ์ •์˜ ํ•ต์‹ฌ ํฌ์ธํŠธ

1. interface vs type: ๋‘˜ ๋‹ค ์“ธ ์ˆ˜ ์žˆ๋Š”๋ฐ, interface๊ฐ€ ํ™•์žฅ์„ฑ์ด ์ข‹์•„์„œ ์ปดํฌ๋„ŒํŠธ Props์—๋Š” ์ฃผ๋กœ interface๋ฅผ ์จ

2. ์„ ํƒ์  ์†์„ฑ: ? ๊ธฐํ˜ธ๋กœ ํ•„์ˆ˜๊ฐ€ ์•„๋‹Œ props ํ‘œ์‹œ

3. ๊ธฐ๋ณธ๊ฐ’: ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น์—์„œ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ • ๊ฐ€๋Šฅ

4. React.FC: ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…, children์„ ์ž๋™์œผ๋กœ ํฌํ•จ

๐Ÿ”„ useState์™€ ํƒ€์ž… ์ถ”๋ก 

useState ํ›…์„ ์“ธ ๋•Œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ง„์งœ ๋น›์„ ๋ฐœํ•ด. ์˜ˆ์ œ ๋ณด๋ฉด ๋ฐ”๋กœ ์ดํ•ด๋  ๊ฑฐ์•ผ:

import React, { useState } from 'react';
import { View, TextInput, Text } from 'react-native';

// ์‚ฌ์šฉ์ž ์ •๋ณด ํƒ€์ž… ์ •์˜
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;
}

const UserForm: React.FC = () => {
  // ํƒ€์ž… ๋ช…์‹œ์  ์ง€์ •
  const [user, setUser] = useState<User>({
    id: 0,
    name: '',
    email: '',
  });

  // ํƒ€์ž… ์ถ”๋ก  (๊ฐ„๋‹จํ•œ ๊ฒฝ์šฐ)
  const [isLoading, setIsLoading] = useState(false); // boolean์œผ๋กœ ์ž๋™ ์ถ”๋ก 
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  // ๋ฐฐ์—ด ํƒ€์ž…
  const [users, setUsers] = useState<User[]>([]);

  const handleNameChange = (text: string) => {
    setUser(prev => ({
      ...prev,
      name: text, // ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ
    }));
  };

  return (
    <View>
      <TextInput
        value={user.name}
        onChangeText={handleNameChange}
        placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”"
      />
      {errorMessage && <Text>{errorMessage}</Text>}
    </View>
  );
};
๐Ÿ’ก useState ํƒ€์ž… ์ง€์ • ๊ฟ€ํŒ
์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ํƒ€์ž…์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด ์ œ๋„ค๋ฆญ ์ƒ๋žต ๊ฐ€๋Šฅ! ์˜ˆ: useState(0)์€ ์ž๋™์œผ๋กœ number ํƒ€์ž…. ํ•˜์ง€๋งŒ ๋ณต์žกํ•œ ๊ฐ์ฒด๋‚˜ null ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์œผ๋ฉด ๋ช…์‹œ์ ์œผ๋กœ ํƒ€์ž… ์ง€์ •ํ•˜๋Š” ๊ฒŒ ์ข‹์•„.

๐ŸŽฃ useRef์™€ ํƒ€์ž… ์•ˆ์ „์„ฑ

useRef๋Š” ์ข€ ๊นŒ๋‹ค๋กœ์šด๋ฐ, ์ œ๋Œ€๋กœ ์•Œ๋ฉด ์—„์ฒญ ์œ ์šฉํ•ด:

import React, { useRef, useEffect } from 'react';
import { TextInput, View } from 'react-native';

const FocusExample: React.FC = () => {
  // TextInput ref ํƒ€์ž… ์ง€์ •
  const inputRef = useRef<TextInput>(null);

  // ์ˆซ์ž ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ref
  const countRef = useRef<number>(0);

  useEffect(() => {
    // null ์ฒดํฌ ํ›„ ์‚ฌ์šฉ
    if (inputRef.current) {
      inputRef.current.focus();
    }

    // ๊ฐ’ ์—…๋ฐ์ดํŠธ
    countRef.current += 1;
  }, []);

  return (
    <View>
      <TextInput
        ref={inputRef}
        placeholder="์ž๋™ ํฌ์ปค์Šค๋จ"
      />
    </View>
  );
};

์—ฌ๊ธฐ์„œ useRef<TextInput>(null)์ฒ˜๋Ÿผ ์ œ๋„ค๋ฆญ์œผ๋กœ ํƒ€์ž…์„ ์ง€์ •ํ•˜๊ณ  ์ดˆ๊ธฐ๊ฐ’์„ null๋กœ ์ฃผ๋ฉด, ref.current๋Š” TextInput | null ํƒ€์ž…์ด ๋ผ. ๊ทธ๋ž˜์„œ ์‚ฌ์šฉ ์ „์— null ์ฒดํฌ๋ฅผ ํ•ด์•ผ ํ•ด! ๐Ÿ”

๐ŸŒ Navigation ํƒ€์ž… ์ •์˜ํ•˜๊ธฐ

React Navigation์€ React Native์—์„œ ํ•„์ˆ˜์ธ๋ฐ, ํƒ€์ž… ์ •์˜๊ฐ€ ์ข€ ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์–ด. ๊ทผ๋ฐ ํ•œ ๋ฒˆ๋งŒ ์ œ๋Œ€๋กœ ์„ค์ •ํ•˜๋ฉด ์ง„์งœ ํŽธํ•ด! ๐Ÿ˜Š

๐Ÿ“ฑ Stack Navigator ํƒ€์ž… ์ •์˜

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';

// ์Šคํƒ ๋„ค๋น„๊ฒŒ์ดํ„ฐ์˜ ๋ชจ๋“  ํ™”๋ฉด๊ณผ ํŒŒ๋ผ๋ฏธํ„ฐ ์ •์˜
type RootStackParamList = {
  Home: undefined; // ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ
  Profile: { userId: string; userName: string }; // ํ•„์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ
  Settings: { theme?: 'light' | 'dark' }; // ์„ ํƒ์  ํŒŒ๋ผ๋ฏธํ„ฐ
  Detail: { itemId: number };
};

// Stack Navigator ์ƒ์„ฑ
const Stack = createNativeStackNavigator<RootStackParamList>();

// ๊ฐ ํ™”๋ฉด์˜ Props ํƒ€์ž… ์ •์˜
type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;

// Home ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ
const HomeScreen: React.FC<HomeScreenProps> = ({ navigation }) => {
  const navigateToProfile = () => {
    // ํƒ€์ž… ์•ˆ์ „ํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜!
    navigation.navigate('Profile', {
      userId: '123',
      userName: 'John Doe',
    });
    
    // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์—๋Ÿฌ! ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…์ด ์•ˆ ๋งž์Œ
    // navigation.navigate('Profile', { userId: 123 }); // โŒ
  };

  return (
    <View>
      <CustomButton title="ํ”„๋กœํ•„ ๋ณด๊ธฐ" onPress={navigateToProfile} />
    </View>
  );
};

// Profile ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ
const ProfileScreen: React.FC<ProfileScreenProps> = ({ route, navigation }) => {
  // route.params๊ฐ€ ํƒ€์ž… ์•ˆ์ „!
  const { userId, userName } = route.params;

  return (
    <View>
      <Text>User ID: {userId}</Text>
      <Text>Name: {userName}</Text>
    </View>
  );
};
๐ŸŽฏ Navigation ํƒ€์ž… ์ •์˜์˜ ์žฅ์ 
  • โœ… ํ™”๋ฉด ์ด๋ฆ„ ์˜คํƒ€ ๋ฐฉ์ง€ (์ž๋™์™„์„ฑ ์ง€์›)
  • โœ… ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ์ฒดํฌ (์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ฐฉ์ง€)
  • โœ… ํ•„์ˆ˜/์„ ํƒ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„
  • โœ… IDE์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ตฌ์กฐ ์ฆ‰์‹œ ํ™•์ธ ๊ฐ€๋Šฅ

๐Ÿ—‚๏ธ Tab Navigator์™€ Drawer Navigator

ํƒญ์ด๋‚˜ ๋“œ๋กœ์–ด ๋„ค๋น„๊ฒŒ์ดํ„ฐ๋„ ๋˜‘๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด:

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';

type TabParamList = {
  Home: undefined;
  Search: { query?: string };
  Notifications: undefined;
  Profile: { userId: string };
};

const Tab = createBottomTabNavigator<TabParamList>();

// ๋ณตํ•ฉ ๋„ค๋น„๊ฒŒ์ดํ„ฐ ํƒ€์ž… (Stack + Tab)
type RootStackParamList = {
  MainTabs: NavigatorScreenParams<TabParamList>;
  Modal: { content: string };
};

// ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ค‘์ฒฉ๋œ ๋„ค๋น„๊ฒŒ์ดํ„ฐ๋„ ํƒ€์ž… ์•ˆ์ „!

๐Ÿ”Œ API ํ†ต์‹ ๊ณผ ํƒ€์ž… ์ •์˜

๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ API ํ†ต์‹ ์€ ํ•„์ˆ˜์ง€? ์—ฌ๊ธฐ์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ง„์งœ ๋น›์„ ๋ฐœํ•ด! ๐ŸŒŸ

๐Ÿ“ก API ์‘๋‹ต ํƒ€์ž… ์ •์˜

// API ์‘๋‹ต ํƒ€์ž… ์ •์˜
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  error?: string;
}

// ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ํƒ€์ž…
interface UserData {
  id: number;
  username: string;
  email: string;
  profile: {
    avatar: string;
    bio: string;
    followers: number;
  };
  createdAt: string;
}

// ๊ฒŒ์‹œ๋ฌผ ๋ฐ์ดํ„ฐ ํƒ€์ž…
interface Post {
  id: number;
  title: string;
  content: string;
  author: UserData;
  likes: number;
  comments: Comment[];
  createdAt: string;
}

interface Comment {
  id: number;
  text: string;
  author: UserData;
  createdAt: string;
}

// API ํ˜ธ์ถœ ํ•จ์ˆ˜
const fetchUser = async (userId: string): Promise<ApiResponse<UserData>> => {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const data = await response.json();
    return data;
  } catch (error) {
    throw new Error('Failed to fetch user');
  }
};

// ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
const UserProfile: React.FC = () => {
  const [user, setUser] = useState<UserData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const loadUser = async () => {
      try {
        const response = await fetchUser('123');
        if (response.success) {
          setUser(response.data); // ํƒ€์ž… ์•ˆ์ „!
        } else {
          setError(response.error || 'Unknown error');
        }
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Unknown error');
      } finally {
        setLoading(false);
      }
    };

    loadUser();
  }, []);

  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;
  if (!user) return <Text>No user found</Text>;

  return (
    <View>
      <Text>{user.username}</Text>
      <Text>{user.email}</Text>
      <Text>Followers: {user.profile.followers}</Text>
    </View>
  );
};
๐Ÿš€ ์‹ค์ „ ๊ฟ€ํŒ: ์ œ๋„ค๋ฆญ ํ™œ์šฉ
ApiResponse<T>์ฒ˜๋Ÿผ ์ œ๋„ค๋ฆญ์„ ์“ฐ๋ฉด ๋‹ค์–‘ํ•œ API ์‘๋‹ต์— ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ! ์ฝ”๋“œ ์ค‘๋ณต์„ ํ™• ์ค„์ผ ์ˆ˜ ์žˆ์–ด.

๐Ÿ›ก๏ธ Axios์™€ ํƒ€์ž… ์•ˆ์ „์„ฑ

Axios๋ฅผ ์“ด๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ํƒ€์ž…์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด:

import axios, { AxiosResponse } from 'axios';

// Axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// ํƒ€์ž… ์•ˆ์ „ํ•œ API ํด๋ž˜์Šค
class UserAPI {
  static async getUser(userId: string): Promise<UserData> {
    const response: AxiosResponse<ApiResponse<UserData>> = 
      await api.get(`/users/${userId}`);
    return response.data.data;
  }

  static async updateUser(
    userId: string,
    updates: Partial<UserData>
  ): Promise<UserData> {
    const response: AxiosResponse<ApiResponse<UserData>> = 
      await api.patch(`/users/${userId}`, updates);
    return response.data.data;
  }

  static async deleteUser(userId: string): Promise<void> {
    await api.delete(`/users/${userId}`);
  }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const updateUserProfile = async () => {
  try {
    const updatedUser = await UserAPI.updateUser('123', {
      username: 'newUsername', // ํƒ€์ž… ์ฒดํฌ๋จ!
      // invalidField: 'test', // โŒ ์—๋Ÿฌ! UserData์— ์—†๋Š” ํ•„๋“œ
    });
    console.log('Updated:', updatedUser);
  } catch (error) {
    console.error('Update failed:', error);
  }
};

์—ฌ๊ธฐ์„œ Partial<UserData>๋Š” UserData์˜ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์„ ํƒ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ค˜. ์—…๋ฐ์ดํŠธํ•  ๋•Œ ์ผ๋ถ€ ํ•„๋“œ๋งŒ ๋ณด๋‚ด๋„ ๋˜๋‹ˆ๊นŒ ์™„์ „ ์œ ์šฉํ•˜์ง€! ๐Ÿ‘

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž… ํ™œ์šฉ ๊ธฐ๋ณธ ์œ ํ‹ธ๋ฆฌํ‹ฐ Partial<T> Required<T> Readonly<T> Pick<T,K> Omit<T,K> Record<K,T> ๊ณ ๊ธ‰ ์œ ํ‹ธ๋ฆฌํ‹ฐ Extract<T,U> Exclude<T,U> NonNullable<T> ReturnType<T> Parameters<T> Awaited<T> React Native ํŠนํ™” StyleProp<ViewStyle> ViewProps, TextProps GestureResponderEvent NativeSyntheticEvent ImageSourcePropType Animated.Value

๐ŸŽญ ๊ณ ๊ธ‰ ํƒ€์ž… ํŒจํ„ด๊ณผ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…

์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋กœ ๋„˜์–ด๊ฐ€๋ณผ๊นŒ? ์‹ค์ „์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํŒจํ„ด๋“ค์ด์•ผ! ๐Ÿ”ฅ

๐Ÿ”ง ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž… ํ™œ์šฉํ•˜๊ธฐ

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…๋“ค์€ ์ง„์งœ ๊ฟ€์ด์•ผ. ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž:

// 1. Partial - ๋ชจ๋“  ์†์„ฑ์„ ์„ ํƒ์ ์œผ๋กœ
interface UserSettings {
  theme: 'light' | 'dark';
  notifications: boolean;
  language: string;
  fontSize: number;
}

// ์ผ๋ถ€๋งŒ ์—…๋ฐ์ดํŠธํ•  ๋•Œ
const updateSettings = (updates: Partial<UserSettings>) => {
  // theme๋งŒ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜, notifications๋งŒ ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ
};

updateSettings({ theme: 'dark' }); // โœ…
updateSettings({ fontSize: 16, language: 'ko' }); // โœ…

// 2. Pick - ํŠน์ • ์†์„ฑ๋งŒ ์„ ํƒ
type UserBasicInfo = Pick<UserData, 'id' | 'username' | 'email'>;

const displayBasicInfo = (user: UserBasicInfo) => {
  // id, username, email๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  console.log(user.username);
  // console.log(user.profile); // โŒ ์—๋Ÿฌ!
};

// 3. Omit - ํŠน์ • ์†์„ฑ ์ œ์™ธ
type UserWithoutSensitiveData = Omit<UserData, 'email' | 'profile'>;

const publicUserData: UserWithoutSensitiveData = {
  id: 1,
  username: 'john_doe',
  createdAt: '2024-01-01',
  // email๊ณผ profile์€ ์ œ์™ธ๋จ
};

// 4. Record - ํ‚ค-๊ฐ’ ์Œ ํƒ€์ž… ์ •์˜
type ThemeColors = Record<'primary' | 'secondary' | 'background', string>;

const lightTheme: ThemeColors = {
  primary: '#007AFF',
  secondary: '#5856D6',
  background: '#FFFFFF',
};

// 5. Readonly - ๋ถˆ๋ณ€ ๊ฐ์ฒด
type ImmutableUser = Readonly<UserData>;

const user: ImmutableUser = {
  id: 1,
  username: 'john',
  email: 'john@example.com',
  profile: { avatar: '', bio: '', followers: 0 },
  createdAt: '2024-01-01',
};

// user.username = 'jane'; // โŒ ์—๋Ÿฌ! ์ฝ๊ธฐ ์ „์šฉ
๐Ÿ’Ž ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž… ์‹ค์ „ ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค

Partial: ํผ ์—…๋ฐ์ดํŠธ, ์„ค์ • ๋ณ€๊ฒฝ
Pick: API ์‘๋‹ต์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ถ”์ถœ
Omit: ๋ฏผ๊ฐํ•œ ์ •๋ณด ์ œ์™ธ, ๊ณต๊ฐœ API ์‘๋‹ต
Record: ์„ค์ • ๊ฐ์ฒด, ํ…Œ๋งˆ ์ •์˜, ๋‹ค๊ตญ์–ด ์ง€์›
Readonly: ์ƒ์ˆ˜ ๊ฐ์ฒด, ๋ถˆ๋ณ€ ์ƒํƒœ ๊ด€๋ฆฌ

๐ŸŽจ ์กฐ๊ฑด๋ถ€ ํƒ€์ž…๊ณผ ํƒ€์ž… ๊ฐ€๋“œ

์กฐ๊ฑด๋ถ€ ํƒ€์ž…์€ ์ข€ ์–ด๋ ค์›Œ ๋ณด์ด์ง€๋งŒ, ์•Œ๊ณ  ๋‚˜๋ฉด ์—„์ฒญ ๊ฐ•๋ ฅํ•ด:

// ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ์˜ˆ์ œ
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

// ์‹ค์ „ ์˜ˆ์ œ: API ์‘๋‹ต ํƒ€์ž… ์ž๋™ ์ถ”๋ก 
type ApiEndpoint = 
  | { type: 'user'; data: UserData }
  | { type: 'post'; data: Post }
  | { type: 'comment'; data: Comment };

type ExtractData<T extends ApiEndpoint['type']> = 
  Extract<ApiEndpoint, { type: T }>['data'];

type UserDataType = ExtractData<'user'>; // UserData
type PostDataType = ExtractData<'post'>; // Post

// ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜
const isUser = (data: any): data is UserData => {
  return 'username' in data && 'email' in data;
};

const isPost = (data: any): data is Post => {
  return 'title' in data && 'content' in data;
};

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const handleApiResponse = (response: ApiEndpoint) => {
  if (response.type === 'user') {
    // ์—ฌ๊ธฐ์„œ response.data๋Š” ์ž๋™์œผ๋กœ UserData ํƒ€์ž…!
    console.log(response.data.username);
  } else if (response.type === 'post') {
    // ์—ฌ๊ธฐ์„œ๋Š” Post ํƒ€์ž…!
    console.log(response.data.title);
  }
};
โš ๏ธ ์ฃผ์˜: ํƒ€์ž… ๊ฐ€๋“œ ๋‚จ์šฉ ๊ธˆ์ง€
ํƒ€์ž… ๊ฐ€๋“œ๋Š” ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ๋„ˆ๋ฌด ๋งŽ์ด ์“ฐ๋ฉด ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ ธ. ์ •๋ง ํ•„์š”ํ•œ ๊ณณ์—๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๊ฐ€๋Šฅํ•˜๋ฉด ํƒ€์ž… ์‹œ์Šคํ…œ์ด ์ž๋™์œผ๋กœ ์ถ”๋ก ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

๐ŸŽฏ React Native ์Šคํƒ€์ผ๋ง๊ณผ ํƒ€์ž…

์Šคํƒ€์ผ๋งํ•  ๋•Œ๋„ ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด! ์ด๊ฒŒ ์ง„์งœ ๊ฐœ๋ฐœ ๊ฒฝํ—˜์„ ํ™• ๋ฐ”๊ฟ”์ค˜. ๐ŸŽจ

๐Ÿ’… StyleSheet์™€ ํƒ€์ž…

import { StyleSheet, ViewStyle, TextStyle, ImageStyle } from 'react-native';

// ์Šคํƒ€์ผ ํƒ€์ž… ์ •์˜
interface Styles {
  container: ViewStyle;
  title: TextStyle;
  image: ImageStyle;
  button: ViewStyle;
  buttonText: TextStyle;
}

// ํƒ€์ž… ์•ˆ์ „ํ•œ ์Šคํƒ€์ผ ์ƒ์„ฑ
const styles = StyleSheet.create<Styles>({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#FFFFFF',
    // invalidProperty: 'test', // โŒ ์—๋Ÿฌ!
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333333',
    marginBottom: 16,
  },
  image: {
    width: 100,
    height: 100,
    borderRadius: 50,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
  },
  buttonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
    textAlign: 'center',
  },
});

// ๋™์  ์Šคํƒ€์ผ ์ƒ์„ฑ ํ•จ์ˆ˜
const createButtonStyle = (
  backgroundColor: string,
  size: 'small' | 'medium' | 'large'
): ViewStyle => {
  const sizeMap = {
    small: { paddingVertical: 8, paddingHorizontal: 16 },
    medium: { paddingVertical: 12, paddingHorizontal: 24 },
    large: { paddingVertical: 16, paddingHorizontal: 32 },
  };

  return {
    backgroundColor,
    ...sizeMap[size],
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
  };
};

๐ŸŽจ ํ…Œ๋งˆ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

ํ…Œ๋งˆ ์‹œ์Šคํ…œ์„ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด ๋‹คํฌ๋ชจ๋“œ ๊ฐ™์€ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ:

// ํ…Œ๋งˆ ํƒ€์ž… ์ •์˜
interface Theme {
  colors: {
    primary: string;
    secondary: string;
    background: string;
    surface: string;
    text: string;
    textSecondary: string;
    border: string;
    error: string;
    success: string;
  };
  spacing: {
    xs: number;
    sm: number;
    md: number;
    lg: number;
    xl: number;
  };
  typography: {
    h1: TextStyle;
    h2: TextStyle;
    body: TextStyle;
    caption: TextStyle;
  };
  borderRadius: {
    sm: number;
    md: number;
    lg: number;
    full: number;
  };
}

// ๋ผ์ดํŠธ ํ…Œ๋งˆ
const lightTheme: Theme = {
  colors: {
    primary: '#007AFF',
    secondary: '#5856D6',
    background: '#FFFFFF',
    surface: '#F2F2F7',
    text: '#000000',
    textSecondary: '#8E8E93',
    border: '#C6C6C8',
    error: '#FF3B30',
    success: '#34C759',
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
  },
  typography: {
    h1: { fontSize: 32, fontWeight: 'bold' },
    h2: { fontSize: 24, fontWeight: '600' },
    body: { fontSize: 16, fontWeight: 'normal' },
    caption: { fontSize: 12, fontWeight: 'normal' },
  },
  borderRadius: {
    sm: 4,
    md: 8,
    lg: 16,
    full: 9999,
  },
};

// ๋‹คํฌ ํ…Œ๋งˆ
const darkTheme: Theme = {
  ...lightTheme,
  colors: {
    ...lightTheme.colors,
    background: '#000000',
    surface: '#1C1C1E',
    text: '#FFFFFF',
    textSecondary: '#8E8E93',
  },
};

// ํ…Œ๋งˆ Context
import React, { createContext, useContext, useState } from 'react';

interface ThemeContextType {
  theme: Theme;
  isDark: boolean;
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [isDark, setIsDark] = useState(false);
  const theme = isDark ? darkTheme : lightTheme;

  const toggleTheme = () => setIsDark(prev => !prev);

  return (
    <ThemeContext.Provider value={{ theme, isDark, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// ์ปค์Šคํ…€ ํ›…
export const useTheme = (): ThemeContextType => {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
};

// ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
const ThemedButton: React.FC<{ title: string; onPress: () => void }> = ({ title, onPress }) => {
  const { theme } = useTheme();

  return (
    <TouchableOpacity
      style={{
        backgroundColor: theme.colors.primary,
        padding: theme.spacing.md,
        borderRadius: theme.borderRadius.md,
      }}
      onPress={onPress}
    >
      <Text style={[theme.typography.body, { color: theme.colors.background }]}>
        {title}
      </Text>
    </TouchableOpacity>
  );
};

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ๋งˆ ์ „ํ™˜์ด ์™„์ „ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ๋ผ! IDE์—์„œ ์ž๋™์™„์„ฑ๋„ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›๋˜๊ณ . ๐Ÿ˜Ž

๐Ÿ”ฅ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ ํƒ€์ž… ๊ด€๋ฆฌ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ์–ด๋–ป๊ฒŒ ํƒ€์ž…์„ ๊ด€๋ฆฌํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž! ๐Ÿ“

๐Ÿ“‚ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์˜ˆ์‹œ

src/
โ”œโ”€โ”€ types/
โ”‚   โ”œโ”€โ”€ index.ts              # ๋ชจ๋“  ํƒ€์ž… export
โ”‚   โ”œโ”€โ”€ api.types.ts          # API ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ”œโ”€โ”€ navigation.types.ts   # ๋„ค๋น„๊ฒŒ์ด์…˜ ํƒ€์ž…
โ”‚   โ”œโ”€โ”€ user.types.ts         # ์‚ฌ์šฉ์ž ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ”œโ”€โ”€ post.types.ts         # ๊ฒŒ์‹œ๋ฌผ ๊ด€๋ จ ํƒ€์ž…
โ”‚   โ””โ”€โ”€ common.types.ts       # ๊ณตํ†ต ํƒ€์ž…
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ common/
โ”‚   โ”‚   โ”œโ”€โ”€ Button.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ Input.tsx
โ”‚   โ”‚   โ””โ”€โ”€ Card.tsx
โ”‚   โ””โ”€โ”€ features/
โ”‚       โ”œโ”€โ”€ UserProfile.tsx
โ”‚       โ””โ”€โ”€ PostList.tsx
โ”œโ”€โ”€ screens/
โ”‚   โ”œโ”€โ”€ HomeScreen.tsx
โ”‚   โ”œโ”€โ”€ ProfileScreen.tsx
โ”‚   โ””โ”€โ”€ SettingsScreen.tsx
โ”œโ”€โ”€ services/
โ”‚   โ”œโ”€โ”€ api.service.ts        # API ํ˜ธ์ถœ ๋กœ์ง
โ”‚   โ””โ”€โ”€ storage.service.ts    # ๋กœ์ปฌ ์ €์žฅ์†Œ
โ”œโ”€โ”€ hooks/
โ”‚   โ”œโ”€โ”€ useAuth.ts
โ”‚   โ”œโ”€โ”€ useTheme.ts
โ”‚   โ””โ”€โ”€ useApi.ts
โ”œโ”€โ”€ contexts/
โ”‚   โ”œโ”€โ”€ AuthContext.tsx
โ”‚   โ””โ”€โ”€ ThemeContext.tsx
โ””โ”€โ”€ utils/
    โ”œโ”€โ”€ validators.ts
    โ””โ”€โ”€ formatters.ts

๐Ÿ“ ํƒ€์ž… ํŒŒ์ผ ์ž‘์„ฑ ์˜ˆ์‹œ

types/api.types.ts

// API ๊ณตํ†ต ์‘๋‹ต ํƒ€์ž…
export interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
  error?: ApiError;
}

export interface ApiError {
  code: string;
  message: string;
  details?: Record<string, any>;
}

// ํŽ˜์ด์ง€๋„ค์ด์…˜
export interface PaginatedResponse<T> {
  items: T[];
  total: number;
  page: number;
  pageSize: number;
  hasMore: boolean;
}

// API ์—”๋“œํฌ์ธํŠธ ํƒ€์ž…
export type ApiEndpoints = {
  'GET /users/:id': {
    params: { id: string };
    response: UserData;
  };
  'POST /users': {
    body: CreateUserDto;
    response: UserData;
  };
  'GET /posts': {
    query: { page: number; limit: number };
    response: PaginatedResponse<Post>;
  };
};

types/user.types.ts

export interface UserData {
  id: string;
  username: string;
  email: string;
  profile: UserProfile;
  settings: UserSettings;
  createdAt: string;
  updatedAt: string;
}

export interface UserProfile {
  avatar: string;
  bio: string;
  location?: string;
  website?: string;
  followers: number;
  following: number;
}

export interface UserSettings {
  theme: 'light' | 'dark' | 'auto';
  notifications: NotificationSettings;
  privacy: PrivacySettings;
  language: string;
}

export interface NotificationSettings {
  push: boolean;
  email: boolean;
  sms: boolean;
  frequency: 'realtime' | 'daily' | 'weekly';
}

export interface PrivacySettings {
  profileVisibility: 'public' | 'friends' | 'private';
  showEmail: boolean;
  showLocation: boolean;
}

// DTO (Data Transfer Object)
export interface CreateUserDto {
  username: string;
  email: string;
  password: string;
}

export interface UpdateUserDto extends Partial<Omit<UserData, 'id' | 'createdAt' | 'updatedAt'>> {}

// ํƒ€์ž… ๊ฐ€๋“œ
export const isUserData = (data: any): data is UserData => {
  return (
    typeof data === 'object' &&
    'id' in data &&
    'username' in data &&
    'email' in data
  );
};
๐ŸŽฏ ํƒ€์ž… ํŒŒ์ผ ๊ด€๋ฆฌ ํŒ
1. ๋„๋ฉ”์ธ๋ณ„๋กœ ํƒ€์ž… ํŒŒ์ผ ๋ถ„๋ฆฌ (user, post, comment ๋“ฑ)
2. ๊ณตํ†ต ํƒ€์ž…์€ common.types.ts์— ๋ชจ์•„๋‘๊ธฐ
3. API ๊ด€๋ จ ํƒ€์ž…์€ ๋ณ„๋„ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌ
4. DTO์™€ ์—”ํ‹ฐํ‹ฐ ํƒ€์ž… ๊ตฌ๋ถ„ํ•˜๊ธฐ
5. ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋„ ๊ฐ™์€ ํŒŒ์ผ์— ์ž‘์„ฑ

๐ŸŽฃ ์ปค์Šคํ…€ ํ›…๊ณผ ํƒ€์ž… ์•ˆ์ „์„ฑ

์ปค์Šคํ…€ ํ›…์„ ๋งŒ๋“ค ๋•Œ๋„ ํƒ€์ž…์„ ์ œ๋Œ€๋กœ ์ •์˜ํ•˜๋ฉด ์žฌ์‚ฌ์šฉ์„ฑ์ด ํ›จ์”ฌ ์ข‹์•„์ ธ! ๐Ÿช

๐Ÿ”Œ useApi ํ›… ๋งŒ๋“ค๊ธฐ

import { useState, useEffect, useCallback } from 'react';

// ํ›… ๋ฐ˜ํ™˜ ํƒ€์ž…
interface UseApiResult<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}

// ํ›… ์˜ต์…˜ ํƒ€์ž…
interface UseApiOptions {
  immediate?: boolean; // ์ฆ‰์‹œ ์‹คํ–‰ ์—ฌ๋ถ€
  onSuccess?: (data: any) => void;
  onError?: (error: string) => void;
}

// ์ œ๋„ค๋ฆญ API ํ›…
function useApi<T>(
  apiFunction: () => Promise<T>,
  options: UseApiOptions = {}
): UseApiResult<T> {
  const { immediate = true, onSuccess, onError } = options;
  
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const fetchData = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const result = await apiFunction();
      setData(result);
      onSuccess?.(result);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
      setError(errorMessage);
      onError?.(errorMessage);
    } finally {
      setLoading(false);
    }
  }, [apiFunction, onSuccess, onError]);

  useEffect(() => {
    if (immediate) {
      fetchData();
    }
  }, [immediate, fetchData]);

  return { data, loading, error, refetch: fetchData };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const UserProfileScreen: React.FC = () => {
  const { data: user, loading, error, refetch } = useApi<UserData>(
    () => UserAPI.getUser('123'),
    {
      onSuccess: (data) => console.log('User loaded:', data),
      onError: (error) => console.error('Failed to load user:', error),
    }
  );

  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;
  if (!user) return <Text>No user found</Text>;

  return (
    <View>
      <Text>{user.username}</Text>
      <CustomButton title="์ƒˆ๋กœ๊ณ ์นจ" onPress={refetch} />
    </View>
  );
};

๐Ÿ“ useForm ํ›… ๋งŒ๋“ค๊ธฐ

interface UseFormOptions<T> {
  initialValues: T;
  validate?: (values: T) => Partial<Record<keyof T, string>>;
  onSubmit: (values: T) => void | Promise<void>;
}

interface UseFormResult<T> {
  values: T;
  errors: Partial<Record<keyof T, string>>;
  touched: Partial<Record<keyof T, boolean>>;
  handleChange: (field: keyof T) => (value: any) => void;
  handleBlur: (field: keyof T) => () => void;
  handleSubmit: () => Promise<void>;
  resetForm: () => void;
  setFieldValue: (field: keyof T, value: any) => void;
  isValid: boolean;
  isSubmitting: boolean;
}

function useForm<T extends Record<string, any>>(
  options: UseFormOptions<T>
): UseFormResult<T> {
  const { initialValues, validate, onSubmit } = options;

  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
  const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = useCallback((field: keyof T) => (value: any) => {
    setValues(prev => ({ ...prev, [field]: value }));
    // ๊ฐ’ ๋ณ€๊ฒฝ ์‹œ ํ•ด๋‹น ํ•„๋“œ ์—๋Ÿฌ ์ œ๊ฑฐ
    if (errors[field]) {
      setErrors(prev => {
        const newErrors = { ...prev };
        delete newErrors[field];
        return newErrors;
      });
    }
  }, [errors]);

  const handleBlur = useCallback((field: keyof T) => () => {
    setTouched(prev => ({ ...prev, [field]: true }));
    
    // ๋ธ”๋Ÿฌ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    if (validate) {
      const validationErrors = validate(values);
      if (validationErrors[field]) {
        setErrors(prev => ({ ...prev, [field]: validationErrors[field] }));
      }
    }
  }, [validate, values]);

  const handleSubmit = useCallback(async () => {
    // ๋ชจ๋“  ํ•„๋“œ๋ฅผ touched๋กœ ํ‘œ์‹œ
    const allTouched = Object.keys(values).reduce(
      (acc, key) => ({ ...acc, [key]: true }),
      {} as Partial<Record<keyof T, boolean>>
    );
    setTouched(allTouched);

    // ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    if (validate) {
      const validationErrors = validate(values);
      setErrors(validationErrors);
      
      if (Object.keys(validationErrors).length > 0) {
        return;
      }
    }

    // ์ œ์ถœ
    try {
      setIsSubmitting(true);
      await onSubmit(values);
    } catch (error) {
      console.error('Form submission error:', error);
    } finally {
      setIsSubmitting(false);
    }
  }, [values, validate, onSubmit]);

  const resetForm = useCallback(() => {
    setValues(initialValues);
    setErrors({});
    setTouched({});
  }, [initialValues]);

  const setFieldValue = useCallback((field: keyof T, value: any) => {
    setValues(prev => ({ ...prev, [field]: value }));
  }, []);

  const isValid = Object.keys(errors).length === 0;

  return {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    handleSubmit,
    resetForm,
    setFieldValue,
    isValid,
    isSubmitting,
  };
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
interface LoginFormValues {
  email: string;
  password: string;
}

const LoginScreen: React.FC = () => {
  const form = useForm<LoginFormValues>({
    initialValues: {
      email: '',
      password: '',
    },
    validate: (values) => {
      const errors: Partial<Record<keyof LoginFormValues, string>> = {};
      
      if (!values.email) {
        errors.email = '์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”';
      } else if (!/\S+@\S+\.\S+/.test(values.email)) {
        errors.email = '์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค';
      }
      
      if (!values.password) {
        errors.password = '๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”';
      } else if (values.password.length < 6) {
        errors.password = '๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค';
      }
      
      return errors;
    },
    onSubmit: async (values) => {
      console.log('Login:', values);
      // API ํ˜ธ์ถœ
    },
  });

  return (
    <View>
      <TextInput
        value={form.values.email}
        onChangeText={form.handleChange('email')}
        onBlur={form.handleBlur('email')}
        placeholder="์ด๋ฉ”์ผ"
      />
      {form.touched.email && form.errors.email && (
        <Text style={{ color: 'red' }}>{form.errors.email}</Text>
      )}

      <TextInput
        value={form.values.password}
        onChangeText={form.handleChange('password')}
        onBlur={form.handleBlur('password')}
        placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ"
        secureTextEntry
      />
      {form.touched.password && form.errors.password && (
        <Text style={{ color: 'red' }}>{form.errors.password}</Text>
      )}

      <CustomButton
        title={form.isSubmitting ? '๋กœ๊ทธ์ธ ์ค‘...' : '๋กœ๊ทธ์ธ'}
        onPress={form.handleSubmit}
        disabled={!form.isValid || form.isSubmitting}
      />
    </View>
  );
};

์ด useForm ํ›…์€ ์™„์ „ ํƒ€์ž… ์•ˆ์ „ํ•˜๋ฉด์„œ๋„ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด! ์–ด๋–ค ํผ์—๋“  ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๐ŸŽ‰

๐Ÿšจ ํ”ํ•œ ํƒ€์ž… ์—๋Ÿฌ์™€ ํ•ด๊ฒฐ๋ฒ•

๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ํƒ€์ž… ์—๋Ÿฌ๋“ค์ด ์žˆ์–ด. ํ•˜๋‚˜์”ฉ ํ•ด๊ฒฐ๋ฒ•์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ”ง

โŒ ์—๋Ÿฌ 1: Type 'X' is not assignable to type 'Y'

// ๋ฌธ์ œ ์ƒํ™ฉ
interface User {
  id: number;
  name: string;
}

const user = {
  id: 1,
  name: 'John',
  age: 30, // ์ถ”๊ฐ€ ์†์„ฑ
};

const validUser: User = user; // โŒ ์—๋Ÿฌ!

// ํ•ด๊ฒฐ๋ฒ• 1: ํƒ€์ž… ๋‹จ์–ธ
const validUser: User = user as User; // โœ…

// ํ•ด๊ฒฐ๋ฒ• 2: ํ•„์š”ํ•œ ์†์„ฑ๋งŒ ์ถ”์ถœ
const validUser: User = {
  id: user.id,
  name: user.name,
}; // โœ…

// ํ•ด๊ฒฐ๋ฒ• 3: Pick ์œ ํ‹ธ๋ฆฌํ‹ฐ ํƒ€์ž…
const validUser: User = (({ id, name }) => ({ id, name }))(user); // โœ…

โŒ ์—๋Ÿฌ 2: Object is possibly 'null' or 'undefined'

// ๋ฌธ์ œ ์ƒํ™ฉ
const user: User | null = getUser();
console.log(user.name); // โŒ ์—๋Ÿฌ!

// ํ•ด๊ฒฐ๋ฒ• 1: Optional Chaining
console.log(user?.name); // โœ…

// ํ•ด๊ฒฐ๋ฒ• 2: Nullish Coalescing
const userName = user?.name ?? 'Guest'; // โœ…

// ํ•ด๊ฒฐ๋ฒ• 3: ํƒ€์ž… ๊ฐ€๋“œ
if (user !== null) {
  console.log(user.name); // โœ… ์ด ๋ธ”๋ก ์•ˆ์—์„œ๋Š” user๊ฐ€ null์ด ์•„๋‹˜
}

// ํ•ด๊ฒฐ๋ฒ• 4: Non-null Assertion (ํ™•์‹คํ•  ๋•Œ๋งŒ!)
console.log(user!.name); // โœ… ํ•˜์ง€๋งŒ ์œ„ํ—˜ํ•  ์ˆ˜ ์žˆ์Œ

โŒ ์—๋Ÿฌ 3: Property 'X' does not exist on type 'Y'

// ๋ฌธ์ œ ์ƒํ™ฉ
const data: any = fetchData();
console.log(data.user.name); // any ํƒ€์ž…์ด๋ผ ์—๋Ÿฌ๋Š” ์•ˆ ๋‚˜์ง€๋งŒ ์œ„ํ—˜!

// ํ•ด๊ฒฐ๋ฒ• 1: ํƒ€์ž… ์ •์˜
interface ApiData {
  user: {
    name: string;
    email: string;
  };
}

const data: ApiData = fetchData();
console.log(data.user.name); // โœ…

// ํ•ด๊ฒฐ๋ฒ• 2: ํƒ€์ž… ๊ฐ€๋“œ๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ ‘๊ทผ
const hasUserName = (data: any): data is { user: { name: string } } => {
  return data && data.user && typeof data.user.name === 'string';
};

if (hasUserName(data)) {
  console.log(data.user.name); // โœ…
}

โŒ ์—๋Ÿฌ 4: Argument of type 'X' is not assignable to parameter of type 'Y'

// ๋ฌธ์ œ ์ƒํ™ฉ
const handlePress = (id: number) => {
  console.log(id);
};

const userId = '123'; // string
handlePress(userId); // โŒ ์—๋Ÿฌ!

// ํ•ด๊ฒฐ๋ฒ• 1: ํƒ€์ž… ๋ณ€ํ™˜
handlePress(Number(userId)); // โœ…

// ํ•ด๊ฒฐ๋ฒ• 2: ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ˆ˜์ •
const handlePress = (id: number | string) => {
  const numericId = typeof id === 'string' ? Number(id) : id;
  console.log(numericId);
}; // โœ…

// ํ•ด๊ฒฐ๋ฒ• 3: ์ œ๋„ค๋ฆญ ์‚ฌ์šฉ
const handlePress = <T extends number | string>(id: T) => {
  console.log(id);
}; // โœ…
โš ๏ธ any ํƒ€์ž… ์‚ฌ์šฉ ์ฃผ์˜!
ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ๊ท€์ฐฎ๋‹ค๊ณ  any๋ฅผ ๋‚จ๋ฐœํ•˜๋ฉด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์“ฐ๋Š” ์˜๋ฏธ๊ฐ€ ์—†์–ด์ ธ. ์ •๋ง ์–ด์ฉ” ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด unknown์„ ์“ฐ๊ฑฐ๋‚˜ ์ œ๋Œ€๋กœ ํƒ€์ž…์„ ์ •์˜ํ•˜์ž!
ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ฐœ๋ฐœ ์›Œํฌํ”Œ๋กœ์šฐ 1. ํƒ€์ž… ์ •์˜ 2. ์ปดํฌ๋„ŒํŠธ 3. ํ…Œ์ŠคํŠธ 4. ๋ฐฐํฌ 5. ์œ ์ง€๋ณด์ˆ˜ ์ง€์†์ ์ธ ํƒ€์ž… ๊ฐœ์„ 

๐ŸŽ“ ์‹ค์ „ ํŒ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๋งˆ์ง€๋ง‰์œผ๋กœ ๋‚ด๊ฐ€ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐฐ์šด ๊ฟ€ํŒ๋“ค์„ ๊ณต์œ ํ• ๊ฒŒ! ์ด๊ฑฐ ์•Œ์•˜์œผ๋ฉด ์ง„์ž‘ ๊ฐœ๋ฐœ์ด ํŽธํ–ˆ์„ ํ…๋ฐ... ๐Ÿ˜…

โœจ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค 10๊ฐ€์ง€

1๏ธโƒฃ ํƒ€์ž… ๋จผ์ €, ๊ตฌํ˜„์€ ๋‚˜์ค‘์—
์ปดํฌ๋„ŒํŠธ๋‚˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ธฐ ์ „์— ํƒ€์ž…๋ถ€ํ„ฐ ์ •์˜ํ•ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„ค๊ณ„๊ฐ€ ๋ช…ํ™•ํ•ด์ง€๊ณ  ๋‚˜์ค‘์— ์ˆ˜์ •ํ•  ์ผ์ด ์ค„์–ด๋“ค์–ด.
2๏ธโƒฃ any ๋Œ€์‹  unknown ์‚ฌ์šฉ
์ •๋ง ํƒ€์ž…์„ ๋ชจ๋ฅผ ๋•Œ๋Š” any ๋Œ€์‹  unknown์„ ์จ. unknown์€ ์‚ฌ์šฉ ์ „์— ํƒ€์ž… ์ฒดํฌ๋ฅผ ๊ฐ•์ œํ•ด์„œ ๋” ์•ˆ์ „ํ•ด.
3๏ธโƒฃ ํƒ€์ž… ๋‹จ์–ธ๋ณด๋‹ค ํƒ€์ž… ๊ฐ€๋“œ
as ํ‚ค์›Œ๋“œ๋กœ ํƒ€์ž…์„ ๋‹จ์–ธํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํƒ€์ž… ๊ฐ€๋“œ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒดํฌํ•˜๋Š” ๊ฒŒ ์ข‹์•„.
4๏ธโƒฃ ์ธํ„ฐํŽ˜์ด์Šค ํ™•์žฅ ํ™œ์šฉ
๋น„์Šทํ•œ ํƒ€์ž…์ด ์—ฌ๋Ÿฌ ๊ฐœ ํ•„์š”ํ•˜๋ฉด ๊ธฐ๋ณธ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ  ํ™•์žฅํ•ด์„œ ์‚ฌ์šฉํ•ด. ์ฝ”๋“œ ์ค‘๋ณต์ด ํ™• ์ค„์–ด๋“ค์–ด!
// ์ข‹์€ ์˜ˆ
interface BaseEntity {
  id: string;
  createdAt: string;
  updatedAt: string;
}

interface User extends BaseEntity {
  username: string;
  email: string;
}

interface Post extends BaseEntity {
  title: string;
  content: string;
  authorId: string;
}
5๏ธโƒฃ ์ œ๋„ค๋ฆญ์œผ๋กœ ์žฌ์‚ฌ์šฉ์„ฑ ๋†’์ด๊ธฐ
๋น„์Šทํ•œ ๋กœ์ง์ด ๋ฐ˜๋ณต๋˜๋ฉด ์ œ๋„ค๋ฆญ์„ ํ™œ์šฉํ•ด์„œ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋‚˜ ์ปดํฌ๋„ŒํŠธ๋กœ ๋งŒ๋“ค์–ด.
6๏ธโƒฃ ์œ ๋‹ˆ์˜จ ํƒ€์ž… ์ ๊ทน ํ™œ์šฉ
์—ฌ๋Ÿฌ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋Š” ๊ฐ’์€ ์œ ๋‹ˆ์˜จ ํƒ€์ž…์œผ๋กœ ๋ช…ํ™•ํžˆ ํ‘œํ˜„ํ•ด. string | number ๊ฐ™์€ ์‹์œผ๋กœ!
7๏ธโƒฃ ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…์œผ๋กœ ์ •ํ™•์„ฑ ๋†’์ด๊ธฐ
๋ฌธ์ž์—ด์ด๋‚˜ ์ˆซ์ž ๋Œ€์‹  ๋ฆฌํ„ฐ๋Ÿด ํƒ€์ž…์„ ์“ฐ๋ฉด ๊ฐ€๋Šฅํ•œ ๊ฐ’์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ์–ด.
// ๋‚˜์œ ์˜ˆ
interface Button {
  size: string; // 'small', 'medium', 'large' ์™ธ์˜ ๊ฐ’๋„ ๊ฐ€๋Šฅ
}

// ์ข‹์€ ์˜ˆ
interface Button {
  size: 'small' | 'medium' | 'large'; // ์ •ํ™•ํžˆ ์ด ์„ธ ๊ฐ€์ง€๋งŒ ๊ฐ€๋Šฅ
}
8๏ธโƒฃ readonly๋กœ ๋ถˆ๋ณ€์„ฑ ๋ณด์žฅ
๋ณ€๊ฒฝ๋˜๋ฉด ์•ˆ ๋˜๋Š” ๊ฐ’์€ readonly๋กœ ํ‘œ์‹œํ•ด์„œ ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•ด.
9๏ธโƒฃ ํƒ€์ž… ๋ณ„์นญ๋ณด๋‹ค ์ธํ„ฐํŽ˜์ด์Šค ์šฐ์„ 
๊ฐ์ฒด ํƒ€์ž…์€ type๋ณด๋‹ค interface๋ฅผ ์“ฐ๋Š” ๊ฒŒ ์ข‹์•„. ํ™•์žฅ์ด ์‰ฝ๊ณ  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋„ ๋” ๋ช…ํ™•ํ•ด.
๐Ÿ”Ÿ strict ๋ชจ๋“œ๋Š” ํ•„์ˆ˜!
tsconfig.json์—์„œ strict ๋ชจ๋“œ๋ฅผ ์ผœ๋‘๋ฉด ์ฒ˜์Œ์—” ํž˜๋“ค์ง€๋งŒ ๋‚˜์ค‘์— ๋ฒ„๊ทธ๊ฐ€ ํ™• ์ค„์–ด๋“ค์–ด.

๐Ÿ” ํƒ€์ž… ๋””๋ฒ„๊น… ํŒ

ํƒ€์ž… ์—๋Ÿฌ๊ฐ€ ์ดํ•ด๊ฐ€ ์•ˆ ๊ฐˆ ๋•Œ ์ด๋ ‡๊ฒŒ ํ•ด๋ด:

// ํƒ€์ž… ํ™•์ธํ•˜๊ธฐ
type CheckType = typeof someVariable;

// ์กฐ๊ฑด๋ถ€ ํƒ€์ž… ๋””๋ฒ„๊น…
type Debug<T> = T extends infer R ? R : never;

// ํƒ€์ž… ์ถœ๋ ฅํ•˜๊ธฐ (์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋กœ ํƒ€์ž… ํ™•์ธ)
type ShowType<T> = T extends never ? never : T;
const test: ShowType<ComplexType> = {} as any; // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์—์„œ ํƒ€์ž… ํ™•์ธ ๊ฐ€๋Šฅ

๐Ÿ“š ์ถ”์ฒœ ๋ฆฌ์†Œ์Šค

๋” ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ์ž๋ฃŒ๋“ค์„ ์ถ”์ฒœํ•ด:

๐Ÿ“– ํ•™์Šต ์ž๋ฃŒ

โ€ข TypeScript ๊ณต์‹ ๋ฌธ์„œ (์ตœ๊ณ ์˜ ๋ ˆํผ๋Ÿฐ์Šค!)
โ€ข React Native ๊ณต์‹ ๋ฌธ์„œ์˜ TypeScript ์„น์…˜
โ€ข Type Challenges (ํƒ€์ž… ์—ฐ์Šต ๋ฌธ์ œ ์‚ฌ์ดํŠธ)
โ€ข Effective TypeScript (์ฑ…)
โ€ข ์žฌ๋Šฅ๋„ท์˜ ๋‹ค๋ฅธ TypeScript ๊ด€๋ จ ์ง€์‹ ์ฝ˜ํ…์ธ ๋“ค

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ React Native์—์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ญ‰ ์‚ดํŽด๋ดค์–ด! ์ฒ˜์Œ์—” ๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์ตํžˆ๋‹ค ๋ณด๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ผ๋Š” ๊ฑธ ๋А๋‚„ ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ. ๐Ÿš€

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ๋‹จ์ˆœํžˆ ํƒ€์ž…์„ ์ฒดํฌํ•˜๋Š” ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ์•ผ. ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ํ•˜๊ณ , ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ๋ฐฉ์ง€ํ•˜๊ณ , ํŒ€์›๋“ค๊ณผ์˜ ํ˜‘์—…์„ ์›ํ™œํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ํŒŒํŠธ๋„ˆ์ง€. ํŠนํžˆ ๋ชจ๋ฐ”์ผ ์•ฑ์ฒ˜๋Ÿผ ๋ณต์žกํ•œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋”๋”์šฑ ๊ทธ ๊ฐ€์น˜๊ฐ€ ๋น›์„ ๋ฐœํ•ด! โœจ

์ฒ˜์Œ์—” ํƒ€์ž… ์—๋Ÿฌ ๋•Œ๋ฌธ์— ๋‹ต๋‹ตํ•  ์ˆ˜๋„ ์žˆ์–ด. "๊ทธ๋ƒฅ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ํ•˜๋ฉด ๋˜๋Š”๋ฐ ์™œ ์ด๋ ‡๊ฒŒ ๋ณต์žกํ•˜๊ฒŒ ํ•˜์ง€?" ์‹ถ์„ ์ˆ˜๋„ ์žˆ๊ณ . ๊ทผ๋ฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์ง€๊ณ , ํŒ€์›์ด ๋Š˜์–ด๋‚˜๊ณ , ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด... ๊ทธ๋•Œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ์ง„๊ฐ€๋ฅผ ์•Œ๊ฒŒ ๋  ๊ฑฐ์•ผ. ๐Ÿ˜Š

๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท์—์„œ ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ๋„, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋•๋ถ„์— ํด๋ผ์ด์–ธํŠธ์™€์˜ ์‹ ๋ขฐ๋„ ๋†’์•„์ง€๊ณ  ๊ฐœ๋ฐœ ์†๋„๋„ ๋นจ๋ผ์กŒ์–ด. ์ฝ”๋“œ ๋ฆฌ๋ทฐํ•  ๋•Œ๋„ ํƒ€์ž…๋งŒ ๋ณด๋ฉด ๋Œ€์ถฉ ๋ญ ํ•˜๋Š” ์ฝ”๋“œ์ธ์ง€ ๋ฐ”๋กœ ์ดํ•ด๋˜๋‹ˆ๊นŒ ์ •๋ง ํŽธํ•˜๋”๋ผ๊ณ !

์ž, ์ด์ œ ๋„ˆ๋„ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ๋ฉ‹์ง„ React Native ์•ฑ์„ ๋งŒ๋“ค์–ด๋ด! ์ฒ˜์Œ์—” ์ฒœ์ฒœํžˆ, ํ•˜๋‚˜์”ฉ ์ ์šฉํ•ด๊ฐ€๋ฉด์„œ ์ต์ˆ™ํ•ด์ง€๋ฉด ๋ผ. ํ™”์ดํŒ…! ๐Ÿ’ช๐Ÿ”ฅ

๐ŸŽ‰ ์ถ•ํ•˜ํ•ด์š”!

์ด์ œ ๋‹น์‹ ์€ React Native์—์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”
์ถฉ๋ถ„ํ•œ ์ง€์‹์„ ๊ฐ–์ถ”๊ฒŒ ๋˜์—ˆ์–ด์š”!

์‹ค์ „์—์„œ ๋ถ€๋”ชํžˆ๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅธ ๊ธธ์ด๋‹ˆ,
์ง€๊ธˆ ๋ฐ”๋กœ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด์„ธ์š”! ๐Ÿš€

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.