StyleSheet과 Flexbox — 모바일 레이아웃의 기본
React Native의 스타일링은 CSS와 비슷하지만, Flexbox 기본값이 다르고 단위 체계도 다릅니다.
웹 CSS를 알고 있으면 React Native 스타일링에 빠르게 적응할 수 있습니다. 하지만 미묘한 기본값 차이 때문에 의도하지 않은 레이아웃이 나오는 경우가 많습니다.
StyleSheet.create()
import { View, Text, StyleSheet } from 'react-native';
function StyledComponent() {
return (
<View style={styles.container}>
<Text style={styles.title}>스타일 적용</Text>
{/* 여러 스타일 합성 */}
<Text style={[styles.text, styles.bold]}>합성된 스타일</Text>
{/* 인라인 스타일 혼합 (뒤의 스타일이 우선) */}
<Text style={[styles.text, { color: 'red' }]}>인라인 혼합</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
text: {
fontSize: 16,
color: '#333',
},
bold: {
fontWeight: 'bold',
},
});
StyleSheet.create()를 쓰는 이유
- **유효성 검사 **: 잘못된 스타일 속성을 빌드 타임에 잡아줌
- ** 성능 **: 스타일 객체가 한 번만 생성되어 참조로 전달
- ** 가독성 **: 스타일을 컴포넌트 로직과 분리
// 조건부 스타일 적용 패턴
function ConditionalStyle({ isActive }: { isActive: boolean }) {
return (
<View style={[
styles.button,
isActive && styles.activeButton,
]}>
<Text style={[
styles.buttonText,
isActive && styles.activeText,
]}>
버튼
</Text>
</View>
);
}
웹 CSS와의 차이점
단위 체계
const styles = StyleSheet.create({
box: {
// 단위 없이 숫자만 사용 — dp(density-independent pixels)
width: 100, // 100dp
height: 50, // 50dp
fontSize: 16, // 16dp
// 퍼센트도 사용 가능
width: '80%',
// px, rem, em, vh, vw 등은 사용 불가!
},
});
지원하지 않는 CSS 속성
float— 사용 불가, Flexbox로 대체display: grid— 사용 불가display: inline— 사용 불가 (flex만 지원)- CSS 애니메이션 — Animated API 사용
- 의사 클래스 (
:hover,:focus) — 사용 불가 - 미디어 쿼리 —
DimensionsAPI 또는useWindowDimensions사용
속성명 차이
const styles = StyleSheet.create({
example: {
// 웹: background-color → React Native: backgroundColor
backgroundColor: '#fff',
// 웹: border-radius → React Native: borderRadius
borderRadius: 8,
// 웹: box-shadow → React Native: 플랫폼별 다름
// iOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
// Android
elevation: 5,
},
});
Flexbox 레이아웃
기본값 차이 (가장 중요!)
| 속성 | 웹 기본값 | React Native 기본값 |
|---|---|---|
flexDirection | row | column |
alignContent | stretch | flex-start |
flexShrink | 1 | 0 |
이 기본값 차이를 모르면 레이아웃이 예상과 다르게 나옵니다. 특히
flexDirection: 'column'이 기본이라는 점,flexShrink: 0이라 콘텐츠가 넘쳐도 자동으로 줄어들지 않는다는 점을 기억하세요.
flex 속성
function FlexExample() {
return (
<View style={{ flex: 1 }}>
{/* 1:2:1 비율로 공간 분배 */}
<View style={{ flex: 1, backgroundColor: 'red' }} />
<View style={{ flex: 2, backgroundColor: 'blue' }} />
<View style={{ flex: 1, backgroundColor: 'green' }} />
</View>
);
}
justifyContent (주축 정렬)
// flexDirection: 'column'일 때 세로 방향 정렬
const styles = StyleSheet.create({
// 위에서부터 배치
start: { justifyContent: 'flex-start' },
// 아래에서부터 배치
end: { justifyContent: 'flex-end' },
// 중앙 배치
center: { justifyContent: 'center' },
// 균등 분배 (양 끝 여백 없음)
between: { justifyContent: 'space-between' },
// 균등 분배 (양 끝 여백 있음)
around: { justifyContent: 'space-around' },
// 완전 균등 분배
evenly: { justifyContent: 'space-evenly' },
});
alignItems (교차축 정렬)
// flexDirection: 'column'일 때 가로 방향 정렬
const styles = StyleSheet.create({
stretch: { alignItems: 'stretch' }, // 늘려서 채움 (기본값)
start: { alignItems: 'flex-start' }, // 왼쪽 정렬
end: { alignItems: 'flex-end' }, // 오른쪽 정렬
center: { alignItems: 'center' }, // 가운데 정렬
});
실전 레이아웃 패턴
// 패턴 1: 화면 중앙 배치
const centered = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
// 패턴 2: 헤더 - 본문 - 푸터
const layout = StyleSheet.create({
container: { flex: 1 },
header: { height: 60 }, // 고정 높이
body: { flex: 1 }, // 나머지 공간 차지
footer: { height: 80 }, // 고정 높이
});
// 패턴 3: 가로 배치 + 간격
const row = StyleSheet.create({
container: {
flexDirection: 'row',
gap: 12, // RN 0.71+에서 gap 지원
},
item: {
flex: 1,
},
});
// 패턴 4: 절대 위치 (배지, 오버레이 등)
const badge = StyleSheet.create({
container: {
position: 'relative',
},
badge: {
position: 'absolute',
top: -5,
right: -5,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: 'red',
},
});
flexWrap과 gap
// 태그 목록처럼 줄바꿈이 필요한 레이아웃
function TagList({ tags }: { tags: string[] }) {
return (
<View style={styles.tagContainer}>
{tags.map((tag) => (
<View key={tag} style={styles.tag}>
<Text style={styles.tagText}>{tag}</Text>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
tagContainer: {
flexDirection: 'row',
flexWrap: 'wrap', // 자동 줄바꿈
gap: 8, // 아이템 간격
},
tag: {
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: '#e0e0e0',
borderRadius: 16,
},
tagText: {
fontSize: 14,
},
});
반응형 스타일링
import { useWindowDimensions, StyleSheet, View } from 'react-native';
function ResponsiveLayout() {
const { width } = useWindowDimensions();
const isTablet = width >= 768;
return (
<View style={[
styles.container,
{ flexDirection: isTablet ? 'row' : 'column' },
]}>
<View style={[styles.sidebar, isTablet && { width: 250 }]} />
<View style={styles.content} />
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
sidebar: { backgroundColor: '#f0f0f0' },
content: { flex: 1 },
});
그림자 (플랫폼별 처리)
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
borderRadius: 8,
padding: 16,
// iOS 그림자
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
// Android 그림자
android: {
elevation: 4,
},
}),
},
});
정리
StyleSheet.create()로 스타일을 분리하면 성능과 가독성이 좋아집니다- React Native Flexbox의 기본값은 웹과 다릅니다:
flexDirection: 'column',flexShrink: 0 - 단위는
dp(숫자)와%만 사용 가능합니다 - 그림자는 iOS와 Android에서 다르게 처리해야 합니다
gap속성은 RN 0.71+에서 지원됩니다
댓글 로딩 중...