Ver3.0 ๐ ํ์ ์คํฌ๋ฆฝํธ๋ก ๋ชจ๋ฐ์ผ ์ฑ ๋ง๋ค๊ธฐ: React Native ํ์ ์์คํ ์์ ์ ๋ณต

๐ ํ์ ์คํฌ๋ฆฝํธ๋ก ๋ชจ๋ฐ์ผ ์ฑ ๋ง๋ค๊ธฐ: React Native ํ์ ์์คํ ์์ ์ ๋ณต
์น๊ตฌ์ผ, ์ด์ ํ์ ์์ ํ๊ฒ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐํ๋ ๋ฒ ์๋ ค์ค๊ฒ! ๐ฏ
๐ฌ ์์ํ๊ธฐ ์ ์: ์ ํ์ ์คํฌ๋ฆฝํธ๋ก ๋ชจ๋ฐ์ผ ์ฑ์ ๋ง๋ค๊น?
์ผ, ์์งํ ๋งํด์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก๋ง React Native ๊ฐ๋ฐํ๋ค๊ฐ ํ์
์คํฌ๋ฆฝํธ ๋์
ํ๋ฉด ์ฒ์์ ์ข ๊ท์ฐฎ์. ๐
๊ทผ๋ฐ ํ๋ก์ ํธ๊ฐ ์ปค์ง๋ฉด์ "์, ์ด๊ฑฐ ์ง์ง ํ์ํ๊ตฌ๋" ์ถ์ ์๊ฐ์ด ์จ๋ค๋๊น?
๋ด๊ฐ ์ค์ ๋ก ๊ฒช์ ์ผ์ธ๋ฐ, ์ด๋ ๋ API ์๋ต ๊ตฌ์กฐ๊ฐ ๋ฐ๋์์ด. ๊ทธ๋ฐ๋ฐ ์๋ฐ์คํฌ๋ฆฝํธ๋ก๋ง ์์ฑ๋ ์ฝ๋์์๋ ์ฑ์ ์คํํด๋ด์ผ๋ง ์๋ฌ๋ฅผ ๋ฐ๊ฒฌํ ์ ์์์ง. ๊ทผ๋ฐ ํ์
์คํฌ๋ฆฝํธ๋ฅผ ์ฐ๋ฉด? ์ฝ๋ ์์ฑํ๋ ์๊ฐ ๋ฐ๋ก ๋นจ๊ฐ ์ค์ด ์ซ ๊ทธ์ด์ง๋ฉด์ "์ผ, ์ฌ๊ธฐ ํ์
์ ๋ง์!" ํ๊ณ ์๋ ค์ค. ์ด๊ฒ ์ผ๋ง๋ ๊ณ ๋ง์ด์ง ๋ชฐ๋ผ! ๐ฏ
ํ์ ์์ ์ฑ: ๋ฐํ์ ์๋ฌ๋ฅผ ์ปดํ์ผ ํ์์ ๋ฏธ๋ฆฌ ์ก์๋
์๋์์ฑ: 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 ์ปดํฌ๋ํธ ํ์ ์ ์ํ๊ธฐ
์, ์ด์ ์ง์ง ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ด์ผ! 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;
์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ํฌ์ธํธ๋ค์ด ์์ด! ๐ฏ
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(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>
);
};
- โ ํ๋ฉด ์ด๋ฆ ์คํ ๋ฐฉ์ง (์๋์์ฑ ์ง์)
- โ ํ๋ผ๋ฏธํฐ ํ์ ์ฒดํฌ (์๋ชป๋ ๋ฐ์ดํฐ ์ ๋ฌ ๋ฐฉ์ง)
- โ ํ์/์ ํ ํ๋ผ๋ฏธํฐ ๋ช ํํ ๊ตฌ๋ถ
- โ 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์ ๋ชจ๋ ํ๋๋ฅผ ์ ํ์ ์ผ๋ก ๋ง๋ค์ด์ค. ์ ๋ฐ์ดํธํ ๋ ์ผ๋ถ ํ๋๋ง ๋ณด๋ด๋ ๋๋๊น ์์ ์ ์ฉํ์ง! ๐
๐ญ ๊ณ ๊ธ ํ์ ํจํด๊ณผ ์ ํธ๋ฆฌํฐ ํ์
์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ์ ๋ก ๋์ด๊ฐ๋ณผ๊น? ์ค์ ์์ ์ ๋ง ์ ์ฉํ ํจํด๋ค์ด์ผ! ๐ฅ
๐ง ์ ํธ๋ฆฌํฐ ํ์ ํ์ฉํ๊ธฐ
ํ์ ์คํฌ๋ฆฝํธ๊ฐ ์ ๊ณตํ๋ ์ ํธ๋ฆฌํฐ ํ์ ๋ค์ ์ง์ง ๊ฟ์ด์ผ. ํ๋์ฉ ์ดํด๋ณด์:
// 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๋ฅผ ๋จ๋ฐํ๋ฉด ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฐ๋ ์๋ฏธ๊ฐ ์์ด์ ธ. ์ ๋ง ์ด์ฉ ์ ์๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ฉด unknown์ ์ฐ๊ฑฐ๋ ์ ๋๋ก ํ์ ์ ์ ์ํ์!
๐ ์ค์ ํ๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
๋ง์ง๋ง์ผ๋ก ๋ด๊ฐ ์ค์ ํ๋ก์ ํธ์์ ๋ฐฐ์ด ๊ฟํ๋ค์ ๊ณต์ ํ ๊ฒ! ์ด๊ฑฐ ์์์ผ๋ฉด ์ง์ ๊ฐ๋ฐ์ด ํธํ์ ํ ๋ฐ... ๐
โจ ๋ฒ ์คํธ ํ๋ํฐ์ค 10๊ฐ์ง
์ปดํฌ๋ํธ๋ ํจ์๋ฅผ ๋ง๋ค๊ธฐ ์ ์ ํ์ ๋ถํฐ ์ ์ํด. ์ด๋ ๊ฒ ํ๋ฉด ์ค๊ณ๊ฐ ๋ช ํํด์ง๊ณ ๋์ค์ ์์ ํ ์ผ์ด ์ค์ด๋ค์ด.
์ ๋ง ํ์ ์ ๋ชจ๋ฅผ ๋๋ any ๋์ unknown์ ์จ. unknown์ ์ฌ์ฉ ์ ์ ํ์ ์ฒดํฌ๋ฅผ ๊ฐ์ ํด์ ๋ ์์ ํด.
as ํค์๋๋ก ํ์ ์ ๋จ์ธํ๋ ๊ฒ๋ณด๋ค ํ์ ๊ฐ๋ ํจ์๋ฅผ ๋ง๋ค์ด์ ์์ ํ๊ฒ ์ฒดํฌํ๋ ๊ฒ ์ข์.
๋น์ทํ ํ์ ์ด ์ฌ๋ฌ ๊ฐ ํ์ํ๋ฉด ๊ธฐ๋ณธ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค๊ณ ํ์ฅํด์ ์ฌ์ฉํด. ์ฝ๋ ์ค๋ณต์ด ํ ์ค์ด๋ค์ด!
// ์ข์ ์
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;
}
๋น์ทํ ๋ก์ง์ด ๋ฐ๋ณต๋๋ฉด ์ ๋ค๋ฆญ์ ํ์ฉํด์ ํ๋์ ํจ์๋ ์ปดํฌ๋ํธ๋ก ๋ง๋ค์ด.
์ฌ๋ฌ ๊ฐ๋ฅ์ฑ์ด ์๋ ๊ฐ์ ์ ๋์จ ํ์ ์ผ๋ก ๋ช ํํ ํํํด. string | number ๊ฐ์ ์์ผ๋ก!
๋ฌธ์์ด์ด๋ ์ซ์ ๋์ ๋ฆฌํฐ๋ด ํ์ ์ ์ฐ๋ฉด ๊ฐ๋ฅํ ๊ฐ์ ์ ํํ ์ ์์ด.
// ๋์ ์
interface Button {
size: string; // 'small', 'medium', 'large' ์ธ์ ๊ฐ๋ ๊ฐ๋ฅ
}
// ์ข์ ์
interface Button {
size: 'small' | 'medium' | 'large'; // ์ ํํ ์ด ์ธ ๊ฐ์ง๋ง ๊ฐ๋ฅ
}
๋ณ๊ฒฝ๋๋ฉด ์ ๋๋ ๊ฐ์ readonly๋ก ํ์ํด์ ์ค์๋ฅผ ๋ฐฉ์งํด.
๊ฐ์ฒด ํ์ ์ type๋ณด๋ค interface๋ฅผ ์ฐ๋ ๊ฒ ์ข์. ํ์ฅ์ด ์ฝ๊ณ ์๋ฌ ๋ฉ์์ง๋ ๋ ๋ช ํํด.
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
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.