화면 구성 & 핵심 컴포넌트




“React Native는 div 대신 View, p 대신 Text를 씁니다.”
이 글에서는 Expo 앱의 화면을 구성하는 핵심 컴포넌트와 스타일링 방법을 코드와 함께 살펴봅니다.


웹 vs React Native 컴포넌트 비교

React Native는 브라우저가 아닌 네이티브 환경에서 실행되기 때문에,
HTML 태그 대신 React Native 전용 컴포넌트를 사용합니다.

웹 (HTML)React Native역할
<div><View>레이아웃 컨테이너
<p>, <span><Text>텍스트 표시
<img><Image>이미지 표시
<input><TextInput>텍스트 입력
<button><TouchableOpacity> / <Pressable>버튼 / 터치 이벤트
<ul> / <li><FlatList>리스트
overflow: scroll<ScrollView>스크롤 영역

View — 레이아웃의 기본

모든 화면 구성의 기본 단위입니다. 웹의 <div>와 같습니다.

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

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <View style={styles.box}>
        <Text>박스 안의 텍스트</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
    padding: 16,
  },
  box: {
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 16,
    marginBottom: 12,
    shadowColor: '#000',
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 2, // Android 그림자
  },
});

Text — 텍스트 표시

React Native에서는 반드시 <Text> 컴포넌트 안에 텍스트를 넣어야 합니다.
View 안에 직접 문자열을 쓰면 오류가 발생합니다.

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

export default function TextExample() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>제목 텍스트</Text>
      <Text style={styles.body}>본문 텍스트입니다. 길게 쓰면 자동으로 줄바꿈됩니다.</Text>
      <Text style={styles.caption} numberOfLines={1} ellipsizeMode="tail">
        한 줄만 표시하고 넘치면 말줄임표로 처리됩니다...
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16 },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  body: {
    fontSize: 16,
    lineHeight: 24,
    color: '#333',
  },
  caption: {
    fontSize: 12,
    color: '#999',
    marginTop: 4,
  },
});

Image — 이미지 표시

로컬 이미지와 원격 이미지 모두 사용할 수 있습니다.

import { Image, View, StyleSheet } from 'react-native';

export default function ImageExample() {
  return (
    <View style={styles.container}>
      {/* 로컬 이미지 */}
      <Image
        source={require('./assets/images/logo.png')}
        style={styles.localImage}
      />

      {/* 원격 이미지 */}
      <Image
        source={{ uri: 'https://picsum.photos/200' }}
        style={styles.remoteImage}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16, gap: 12 },
  localImage: {
    width: 100,
    height: 100,
  },
  remoteImage: {
    width: '100%',
    height: 200,
    borderRadius: 8,
    resizeMode: 'cover', // cover, contain, stretch
  },
});

원격 이미지는 반드시 widthheight를 지정해야 합니다.


TextInput — 텍스트 입력

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

export default function InputExample() {
  const [value, setValue] = useState('');

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="이름을 입력하세요"
        value={value}
        onChangeText={setValue}
      />
      <Text>입력값: {value}</Text>

      {/* 비밀번호 입력 */}
      <TextInput
        style={styles.input}
        placeholder="비밀번호"
        secureTextEntry
      />

      {/* 여러 줄 입력 */}
      <TextInput
        style={[styles.input, styles.multiline]}
        placeholder="내용을 입력하세요"
        multiline
        numberOfLines={4}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16, gap: 12 },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    backgroundColor: '#fff',
  },
  multiline: {
    height: 100,
    textAlignVertical: 'top', // Android에서 텍스트를 위에서 시작
  },
});

TouchableOpacity / Pressable — 버튼 & 터치

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

export default function ButtonExample() {
  return (
    <View style={styles.container}>
      {/* TouchableOpacity: 터치 시 투명도 변화 */}
      <TouchableOpacity
        style={styles.button}
        onPress={() => alert('버튼 클릭!')}
        activeOpacity={0.7}
      >
        <Text style={styles.buttonText}>TouchableOpacity 버튼</Text>
      </TouchableOpacity>

      {/* Pressable: 더 세밀한 터치 제어 (최신 권장 방식) */}
      <Pressable
        style={({ pressed }) => [
          styles.button,
          pressed && styles.buttonPressed,
        ]}
        onPress={() => alert('Pressable 클릭!')}
      >
        <Text style={styles.buttonText}>Pressable 버튼</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 16, gap: 12 },
  button: {
    backgroundColor: '#007AFF',
    padding: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonPressed: {
    backgroundColor: '#005EC4',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

ScrollView — 스크롤 영역

화면을 넘어가는 콘텐츠를 스크롤할 수 있게 합니다.

import { ScrollView, View, Text, StyleSheet } from 'react-native';

export default function ScrollExample() {
  return (
    <ScrollView style={styles.container}>
      {Array.from({ length: 20 }, (_, i) => (
        <View key={i} style={styles.item}>
          <Text>아이템 {i + 1}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
});

리스트가 많을 경우 ScrollView 대신 FlatList를 사용하세요.
ScrollView는 모든 항목을 한 번에 렌더링해 성능에 부담을 줄 수 있습니다.


FlatList — 성능 좋은 리스트

많은 데이터를 효율적으로 렌더링할 때 사용합니다.
화면에 보이는 항목만 렌더링하기 때문에 성능이 뛰어납니다.

import { FlatList, View, Text, StyleSheet } from 'react-native';

const DATA = [
  { id: '1', title: '첫 번째 항목' },
  { id: '2', title: '두 번째 항목' },
  { id: '3', title: '세 번째 항목' },
  // ...
];

export default function ListExample() {
  return (
    <FlatList
      data={DATA}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.item}>
          <Text style={styles.title}>{item.title}</Text>
        </View>
      )}
      ItemSeparatorComponent={() => <View style={styles.separator} />}
    />
  );
}

const styles = StyleSheet.create({
  item: {
    padding: 16,
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 16,
  },
  separator: {
    height: 1,
    backgroundColor: '#eee',
  },
});

StyleSheet — 스타일링

React Native의 스타일은 CSS와 유사하지만 몇 가지 차이가 있습니다.

import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  // Flexbox가 기본 레이아웃 (웹과 동일한 개념)
  container: {
    flex: 1,                    // 남은 공간 모두 차지
    flexDirection: 'column',    // 기본값 (웹은 row가 기본)
    justifyContent: 'center',   // 주축 정렬
    alignItems: 'center',       // 교차축 정렬
    gap: 12,                    // 자식 간격
  },

  // 크기
  box: {
    width: 100,           // 고정 크기
    width: '50%',         // 비율
    height: 100,
  },

  // 여백
  spacing: {
    margin: 16,           // 외부 여백 (전체)
    marginHorizontal: 16, // 좌우 여백
    marginVertical: 8,    // 상하 여백
    padding: 16,          // 내부 여백
  },

  // 텍스트
  text: {
    fontSize: 16,
    fontWeight: 'bold',   // 'normal', 'bold', '100'~'900'
    color: '#333',
    lineHeight: 24,
    textAlign: 'center',
  },

  // 테두리
  border: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
  },
});

웹 CSS와 다른 점

항목웹 CSSReact Native
단위px, %, rem 등숫자 (dp 단위)
flexDirection 기본값rowcolumn
클래스 적용classNamestyle
캐스케이딩있음없음 (독립적)
하이픈 속성명background-colorbackgroundColor (camelCase)

실전 예제 — 프로필 카드

지금까지 배운 컴포넌트를 조합한 예제입니다.

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

export default function ProfileCard() {
  return (
    <View style={styles.card}>
      <Image
        source={{ uri: 'https://picsum.photos/80' }}
        style={styles.avatar}
      />
      <View style={styles.info}>
        <Text style={styles.name}>홍길동</Text>
        <Text style={styles.bio}>React Native 개발자입니다 👋</Text>
      </View>
      <TouchableOpacity style={styles.button} onPress={() => alert('팔로우!')}>
        <Text style={styles.buttonText}>팔로우</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#fff',
    padding: 16,
    borderRadius: 12,
    margin: 16,
    shadowColor: '#000',
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 3,
    gap: 12,
  },
  avatar: {
    width: 56,
    height: 56,
    borderRadius: 28,
  },
  info: {
    flex: 1,
  },
  name: {
    fontSize: 16,
    fontWeight: '700',
    marginBottom: 2,
  },
  bio: {
    fontSize: 13,
    color: '#666',
  },
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 14,
    paddingVertical: 8,
    borderRadius: 20,
  },
  buttonText: {
    color: '#fff',
    fontSize: 13,
    fontWeight: '600',
  },
});

정리

컴포넌트용도
View레이아웃 컨테이너
Text텍스트 표시
Image이미지 표시
TextInput텍스트 입력
TouchableOpacity / Pressable버튼 / 터치 이벤트
ScrollView스크롤 영역 (소량 콘텐츠)
FlatList리스트 (대량 데이터)

이 7가지 컴포넌트만 잘 활용해도 대부분의 화면을 구성할 수 있습니다.
다음 편에서는 Expo Router로 여러 화면 간 이동을 구현하는 방법을 살펴보겠습니다.


시리즈 목차

  • 1편 Expo 개발 환경 세팅 & 첫 앱 실행
  • 2편 화면 구성 & 핵심 컴포넌트 ← 현재 글
  • 3편 화면 이동 구현 (Expo Router)
  • 4편 상태 관리 & API 연동
  • 5편 디바이스 기능 활용 (카메라, 위치, 알림)
  • 6편 Firebase 연동
  • 7편 빌드 & 앱스토어 배포



댓글 남기기