React Native UI 핵심 패턴 (웹 React 비교)

개요

React 웹 경험이 있는 개발자가 React Native에서 UI를 구현할 때 알아야 할 핵심 차이점을 정리합니다.

주간 캘린더 UI 구현 과정에서 사용한 실제 코드 예시 포함.


1. TouchableOpacity + onPress (터치 이벤트)

웹 React

<button onClick={() => handleClick()} style={{ opacity: 1 }}>
  클릭하기
</button>

React Native

import { TouchableOpacity } from "react-native";

<TouchableOpacity
  onPress={() => onPressToggle?.(work)}  // onClick 대신 onPress
  activeOpacity={0.7}                    // 누를 때 투명도 자동 변경
>
  <Text>터치하기</Text>
</TouchableOpacity>

핵심 차이

항목 웹 React React Native
이벤트 onClick onPress
피드백 CSS :active 상태 activeOpacity prop
컴포넌트 ,

2. 조건부 스타일 적용 (배열 문법)

웹 React

<div className={`card ${isActive ? 'card-active' : ''}`}>

React Native

<TouchableOpacity
  style={[
    styles.dayCard,                              // 기본 스타일
    isToday && styles.dayCardToday,              // 조건 1
    hasScheduled && styles.dayCardScheduled,     // 조건 2
    hasCompleted && styles.dayCardCompleted,     // 조건 3
  ]}
>

패턴: style={[기본스타일, 조건 && 조건부스타일]} - 조건이 false면 무시됨


3. Shadow와 Elevation (iOS/Android 분리)

웹 React

.card {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

React Native

const styles = StyleSheet.create({
  card: {
    // iOS 전용
    shadowColor: "#000",
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.08,
    shadowRadius: 4,

    // Android 전용
    elevation: 2,
  },
});

핵심 포인트

  • iOS: shadowColor, shadowOffset, shadowOpacity, shadowRadius 4개 속성
  • Android: elevation 하나로 처리 (0~24)
  • 크로스 플랫폼: 두 속성을 모두 선언하면 각 플랫폼이 해당 속성만 인식

4. SafeAreaView (노치 대응)

개념

iPhone X 이상의 노치, Dynamic Island, 하단 홈 인디케이터 등으로 인해 콘텐츠가 가려지는 것을 방지합니다.

웹에는 없는 개념 - 웹은 뷰포트가 전체 화면이지만, 모바일은 노치 등 물리적 장애물 고려 필요

사용법

import { SafeAreaView } from "react-native-safe-area-context";

const Screen = () => (
  <SafeAreaView style={{ flex: 1 }}>
    <Header />
    <ScrollView>
      {/* 컨텐츠 */}
    </ScrollView>
  </SafeAreaView>
);

주의: react-native의 SafeAreaView가 아닌 react-native-safe-area-context의 것을 사용해야 Android에서도 동작


5. ScrollView (style vs contentContainerStyle)

웹 React

<div style={{ overflow: "auto", padding: "16px 20px" }}>
  {/* 컨텐츠 */}
</div>

React Native

<ScrollView
  style={styles.scrollView}                    // ScrollView 자체 크기
  contentContainerStyle={styles.scrollContent}  // 내부 컨텐츠 스타일
  showsVerticalScrollIndicator={false}
>
  {/* 컨텐츠 */}
</ScrollView>

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,          // 화면 전체 차지
  },
  scrollContent: {
    paddingTop: 16,
    paddingHorizontal: 20,
    paddingBottom: 40,
    gap: 24,          // 각 섹션 간격
  },
});

두 가지 스타일의 차이

속성 역할 주로 사용하는 스타일
style ScrollView 컨테이너 자체 flex, backgroundColor
contentContainerStyle 내부 컨텐츠 영역 padding, gap, alignItems

가로 스크롤

// 웹: overflow-x: auto
// React Native: horizontal prop
<ScrollView
  horizontal
  showsHorizontalScrollIndicator={false}
  contentContainerStyle={{ gap: 10 }}
>
  {items.map(item => <Card key={item.id} />)}
</ScrollView>

6. @expo/vector-icons (아이콘)

웹 React

import { AiOutlineCalendar } from "react-icons/ai";
<AiOutlineCalendar size={22} />

React Native (Expo)

import { Feather, Ionicons } from "@expo/vector-icons";

<Feather name="calendar" size={22} color={colors.textPrimary} />
<Ionicons name="notifications-outline" size={28} color="#111" />

주요 아이콘 패밀리

패밀리 스타일 예시
Feather 깔끔한 선 아이콘 calendar, chevron-down, edit-2
Ionicons iOS 스타일 notifications-outline, document-text-outline
MaterialCommunityIcons Material Design 다양한 아이콘
FontAwesome 클래식 다양한 아이콘

설치 불필요 - Expo 프로젝트에 기본 포함


7. numberOfLines (텍스트 줄 제한)

웹 React

<div style={{
  display: "-webkit-box",
  WebkitLineClamp: 2,
  WebkitBoxOrient: "vertical",
  overflow: "hidden"
}}>
   텍스트...
</div>

React Native

<Text numberOfLines={2}>
   텍스트가 2줄까지만 표시되고 나머지는 말줄임표...
</Text>

훨씬 간단 - prop 하나로 끝. 웹의 복잡한 CSS line-clamp 불필요


8. flexDirection 기본값 차이

핵심 차이

항목 웹 CSS React Native
기본 flexDirection row (가로) column (세로)
가로 배치 기본값 flexDirection: “row” 명시 필요
세로 배치 flex-direction: column 명시 기본값

실제 사용 예시

const styles = StyleSheet.create({
  // flexDirection 생략 → column (위에서 아래로 쌓임)
  card: {
    padding: 16,
    gap: 12,
  },
  // 가로 배치가 필요하면 명시
  cardHeader: {
    flexDirection: "row",
    alignItems: "center",
    justifyContent: "space-between",
  },
});

9. Image + require() (정적 이미지)

웹 React

import logo from './logo.png';
<img src={logo} alt="logo" />
<img src="https://example.com/logo.png" alt="logo" />

React Native

import { Image } from "react-native";

// 로컬 이미지: require() 사용
<Image
  source={require("../assets/images/icon.png")}
  style={{ width: 40, height: 40 }}
  resizeMode="contain"
/>

// URL 이미지: 객체로 감싸기
<Image
  source={{ uri: "https://example.com/logo.png" }}
  style={{ width: 100, height: 100 }}
/>

resizeMode 옵션

설명
cover 비율 유지, 전체 채움 (일부 잘림 가능)
contain 비율 유지, 전체 보임 (빈 공간 가능)
stretch 비율 무시, 전체 채움
center 중앙 정렬, 크기 조정 없음

주의: 로컬 이미지는 require(), URL 이미지는 { uri: "..." } 형태


빠른 참조 테이블

항목 웹 React React Native
클릭/터치 onClick onPress
터치 피드백 CSS :active activeOpacity
가로 스크롤 overflow-x: auto ScrollView horizontal
세로 스크롤 overflow-y: auto ScrollView
그림자 box-shadow shadowColor • elevation
텍스트 제한 line-clamp numberOfLines
flex 기본방향 row (가로) column (세로)
이미지
아이콘 react-icons @expo/vector-icons
노치 대응 해당 없음 SafeAreaView