React Native의 UI는 웹 개발과 근본적으로 다릅니다. 웹에서는 HTML과 CSS를 사용하지만, React Native에서는 네이티브 컴포넌트를 사용합니다.
웹 개발:
<div>, <span>, <p> 등)React Native:
<View>, <Text>, <Image> 등)React Native의 핵심 컴포넌트들을 이해하는 것이 중요합니다:
<div>와 같은 역할, 컨테이너 역할웹 개발에서는 Styled Components, Emotion, CSS Modules 등 다양한 CSS-in-JS 라이브러리가 인기를 끌고 있습니다. 하지만 React Native에서는 다른 접근 방식이 주류를 이루고 있습니다.
웹에서 인기인 스타일링 방법들:
웹에서의 Styled Components:
// 웹에서는 이렇게 사용
const StyledButton = styled.button`
background-color: ${props => props.primary ? '#007AFF' : '#ccc'};
padding: 12px 24px;
border-radius: 8px;
border: none;
`;
React Native에서 Styled Components 사용 시 문제점:
// React Native에서 Styled Components 사용 예제 (비효율적)
import styled from 'styled-components/native';
const StyledView = styled.View`
background-color: ${props => props.bgColor || '#fff'};
padding: ${props => props.padding || 20}px;
border-radius: 12px;
`;
// 이렇게 사용하면 매번 새로운 스타일 객체 생성
<StyledView bgColor="#f0f0f0" padding={16}>
<Text>Content</Text>
</StyledView>
CSS 속성 제한: React Native는 웹의 모든 CSS 속성을 지원하지 않습니다
// 웹에서는 가능하지만 React Native에서는 불가능
const WebComponent = styled.div`
box-shadow: 0 4px 6px rgba(0,0,0,0.1); // ❌ 지원 안함
background: linear-gradient(45deg, red, blue); // ❌ 지원 안함
transform: skew(10deg); // ❌ 지원 안함
`;
플랫폼 별 차이: iOS와 Android의 스타일 차이를 처리하기 복잡함
// Styled Components로 플랫폼 분기 (복잡함)
const PlatformView = styled.View`
${Platform.OS === 'ios' ? `
shadow-color: #000;
shadow-offset: 0px 2px;
shadow-opacity: 0.1;
shadow-radius: 4px;
` : `
elevation: 4;
`}
`;
React Native에서 가장 최적화된 방법입니다.
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 20,
}
});
StyleSheet의 장점:
현 회사에서는 mpos,pos 기기등 작은 하드웨어 자원으로 리엑티브네이티브 언어로 마이그레이션을 진행중에 있습니다.
해서, 성능상 이슈로 styleSheet로 진행중에 있습니다.
최근 각광받는 방법으로, Tailwind CSS를 React Native에서 사용할 수 있게 해줍니다.
// NativeWind 사용 예제
import { View, Text } from 'react-native';
function Card() {
return (
<View className="bg-white p-4 rounded-lg shadow-md m-4">
<Text className="text-xl font-bold text-gray-800 mb-2">
카드 제목
</Text>
<Text className="text-gray-600">
카드 내용입니다.
</Text>
</View>
);
}
NativeWind의 장점:
성능에 특화된 React Native UI 라이브러리입니다.
import { Button, XStack, YStack } from '@tamagui/core';
function App() {
return (
<YStack padding="$4" space="$3">
<Button theme="blue" size="$4">
Primary Button
</Button>
<XStack space="$2">
<Button variant="outlined" flex={1}>
Cancel
</Button>
<Button theme="green" flex={1}>
Confirm
</Button>
</XStack>
</YStack>
);
}
Tamagui의 특징:
| 방법 | 성능 | 학습곡선 | 유연성 | 생태계 | 추천도 |
|---|---|---|---|---|---|
| StyleSheet | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Styled Components | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
| NativeWind | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Tamagui | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
소규모 프로젝트 (개인 앱, MVP):
// StyleSheet + 간단한 테마 시스템
const theme = {
colors: { primary: '#007AFF', secondary: '#34C759' },
spacing: { sm: 8, md: 16, lg: 24 }
};
const styles = StyleSheet.create({
button: {
backgroundColor: theme.colors.primary,
padding: theme.spacing.md,
}
});
중간 규모 프로젝트:
// NativeWind 사용
<View className="bg-blue-500 p-4 rounded-lg">
<Text className="text-white font-bold">
버튼
</Text>
</View>
대규모 프로젝트:
// StyleSheet + 체계적인 디자인 시스템
// styles/designSystem.js
export const DesignSystem = {
components: {
Button: StyleSheet.create({
primary: { /* styles */ },
secondary: { /* styles */ },
}),
Card: StyleSheet.create({
default: { /* styles */ },
elevated: { /* styles */ },
})
}
};
인라인 스타일 (비추천):
<View style={{backgroundColor: 'red', padding: 10}}>
<Text style={{fontSize: 16, color: 'white'}}>Hello</Text>
</View>
StyleSheet 방식 (권장):
<View style={styles.container}>
<Text style={styles.text}>Hello</Text>
</View>
const styles = StyleSheet.create({
container: {
backgroundColor: 'red',
padding: 10,
},
text: {
fontSize: 16,
color: 'white',
}
});
성능 차이의 이유:
실무에서는 여러 방법을 조합해서 사용하는 것이 효과적입니다:
// 1. 기본 컴포넌트는 StyleSheet
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
}
});
// 2. 동적 스타일은 함수형 스타일
const getDynamicStyle = (isActive) => ({
backgroundColor: isActive ? '#007AFF' : '#ccc',
opacity: isActive ? 1 : 0.6,
});
// 3. 사용 시 조합
<View style={[styles.container, getDynamicStyle(isActive)]}>
<Text>Content</Text>
</View>
이러한 접근 방식을 통해 React Native에서 최적의 성능과 개발 생산성을 동시에 얻을 수 있습니다.
React Native는 CSS Flexbox를 기반으로 한 레이아웃 시스템을 사용합니다. 하지만 기본값이 웹과 다릅니다.
주요 차이점:
flexDirection의 기본값이 column (웹은 row)alignItems의 기본값이 stretchflex의 작동 방식이 약간 다름flexDirection: 주축의 방향을 결정
container: {
flexDirection: 'row', // 가로 방향
flexDirection: 'column', // 세로 방향 (기본값)
}
justifyContent: 주축에서의 정렬
container: {
justifyContent: 'flex-start', // 시작점 정렬
justifyContent: 'center', // 중앙 정렬
justifyContent: 'space-between', // 양 끝과 사이에 균등 배치
justifyContent: 'space-around', // 모든 요소 주위에 균등한 공간
}
alignItems: 교차축에서의 정렬
container: {
alignItems: 'flex-start', // 시작점 정렬
alignItems: 'center', // 중앙 정렬
alignItems: 'flex-end', // 끝점 정렬
alignItems: 'stretch', // 늘여서 채우기 (기본값)
}
헤더 레이아웃:
const styles = StyleSheet.create({
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 15,
backgroundColor: '#fff',
},
logo: {
fontSize: 20,
fontWeight: 'bold',
},
profileImage: {
width: 40,
height: 40,
borderRadius: 20,
}
});
카드 레이아웃:
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 20,
marginHorizontal: 20,
marginVertical: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android 그림자
},
cardContent: {
flex: 1,
}
});
모바일 기기는 다양한 화면 크기를 가지고 있습니다. 효과적인 UI를 위해서는 반응형 디자인이 필수입니다.
import { Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
width: width * 0.9, // 화면 너비의 90%
height: height * 0.3, // 화면 높이의 30%
},
responsiveText: {
fontSize: width > 400 ? 18 : 16, // 화면 크기에 따른 폰트 크기
}
});
화면 방향이나 기기에 따라 동적으로 스타일을 변경할 수 있습니다:
const getResponsiveStyle = () => {
const { width } = Dimensions.get('window');
const isTablet = width > 768;
return StyleSheet.create({
container: {
padding: isTablet ? 40 : 20,
flexDirection: isTablet ? 'row' : 'column',
},
content: {
flex: isTablet ? 0.7 : 1,
}
});
};
이미지는 앱 성능에 큰 영향을 미칩니다. 효과적인 이미지 사용 방법:
적절한 크기 설정:
<Image
source={{uri: 'https://example.com/image.jpg'}}
style={{
width: 100,
height: 100,
resizeMode: 'cover', // 'contain', 'stretch', 'center'
}}
/>
로컬 이미지 사용:
// assets 폴더의 이미지 사용 (권장)
<Image source={require('./assets/logo.png')} />
긴 리스트는 FlatList나 SectionList를 사용하여 성능을 최적화합니다:
<FlatList
data={items}
renderItem={({ item }) => <ItemComponent item={item} />}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
/>
불필요한 리렌더링을 방지하기 위한 최적화:
import React, { memo } from 'react';
const OptimizedComponent = memo(({ data }) => {
return (
<View style={styles.container}>
<Text>{data.title}</Text>
</View>
);
});
각 플랫폼은 고유한 디자인 가이드라인을 가지고 있습니다:
iOS:
Android:
import { Platform } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS === 'ios' ? 20 : 0,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
}
});
시각 장애인을 위한 스크린 리더 지원:
<TouchableOpacity
accessible={true}
accessibilityLabel="프로필 수정 버튼"
accessibilityHint="프로필 편집 화면으로 이동합니다"
accessibilityRole="button"
>
<Text>프로필 수정</Text>
</TouchableOpacity>
충분한 색상 대비를 확보하여 가독성을 높입니다:
const colors = {
primary: '#007AFF', // 충분한 대비
secondary: '#34C759',
text: '#000000', // 배경과 높은 대비
textSecondary: '#666666',
background: '#FFFFFF',
};
UI 컴포넌트를 적절히 분리하여 재사용성을 높입니다:
// Button 컴포넌트 예제
const CustomButton = ({ title, onPress, variant = 'primary' }) => {
return (
<TouchableOpacity
style={[styles.button, styles[variant]]}
onPress={onPress}
>
<Text style={[styles.buttonText, styles[`${variant}Text`]]}>
{title}
</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
},
primary: {
backgroundColor: '#007AFF',
},
secondary: {
backgroundColor: '#34C759',
},
buttonText: {
fontSize: 16,
fontWeight: '600',
},
primaryText: {
color: '#FFFFFF',
},
secondaryText: {
color: '#FFFFFF',
}
});
일관된 디자인을 위한 테마 시스템:
// theme.js
export const theme = {
colors: {
primary: '#007AFF',
secondary: '#34C759',
background: '#FFFFFF',
surface: '#F2F2F2',
text: '#000000',
textSecondary: '#666666',
border: '#E0E0E0',
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
},
typography: {
heading1: {
fontSize: 28,
fontWeight: '700',
},
heading2: {
fontSize: 24,
fontWeight: '600',
},
body: {
fontSize: 16,
fontWeight: '400',
},
caption: {
fontSize: 14,
fontWeight: '400',
}
},
borderRadius: {
sm: 4,
md: 8,
lg: 12,
xl: 16,
}
};
프로젝트가 커질수록 스타일 관리가 중요해집니다:
styles/
├── theme.js // 전역 테마
├── mixins.js // 재사용 가능한 스타일 조각
├── typography.js // 텍스트 스타일
└── components/ // 컴포넌트별 스타일
├── Button.styles.js
├── Card.styles.js
└── Header.styles.js
React Native의 Animated API를 사용한 애니메이션:
import { Animated } from 'react-native';
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}).start();
};
return (
<Animated.View style={{opacity: fadeAnim}}>
<Text>Fade In Animation</Text>
</Animated.View>
);
터치 피드백을 통한 사용자 경험 향상:
<TouchableOpacity
style={styles.button}
activeOpacity={0.7} // 터치 시 투명도 변화
onPress={handlePress}
>
<Text>터치해보세요</Text>
</TouchableOpacity>
Flipper는 React Native 앱의 UI를 실시간으로 확인하고 디버깅할 수 있는 도구입니다:
경계선 표시로 레이아웃 확인:
// 개발 중에만 사용
const debugStyles = __DEV__ ? {
borderWidth: 1,
borderColor: 'red',
} : {};
<View style={[styles.container, debugStyles]}>
콘솔 로그로 스타일 확인:
console.log('Current dimensions:', Dimensions.get('window'));
React Native에서 효과적인 UI를 그리기 위해서는 다음 사항들을 기억해야 합니다: