모바일 앱에서 터치는 마우스 클릭과 다릅니다. 탭, 롱프레스, 더블탭 등 다양한 제스처를 구분해서 처리해야 합니다.

웹에서는 onClick 하나로 충분하지만, 모바일에서는 터치 피드백 이 사용자 경험에 큰 영향을 미칩니다. React Native는 이를 위해 여러 터치 컴포넌트를 제공합니다.


터치 컴포넌트 비교

컴포넌트피드백권장 여부특징
Pressable커스텀권장가장 유연, 최신 API
TouchableOpacity투명도 변화사용 가능가장 널리 사용됨
TouchableHighlight배경색 변화레거시리스트 아이템에 적합
TouchableWithoutFeedback없음비권장피드백 없는 터치
Button플랫폼 기본제한적커스터마이징 불가

Pressable (권장)

React Native 0.63에서 도입된 최신 터치 API입니다. 터치 상태에 따라 세밀한 제어가 가능합니다.

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

function PressableButton() {
  return (
    <Pressable
      onPress={() => console.log('탭!')}
      onLongPress={() => console.log('롱프레스!')}
      onPressIn={() => console.log('손가락 닿음')}
      onPressOut={() => console.log('손가락 뗌')}
      delayLongPress={500} // 롱프레스 인식 시간 (ms)
      // 터치 상태에 따른 동적 스타일
      style={({ pressed }) => [
        styles.button,
        pressed && styles.pressed,
      ]}
    >
      {({ pressed }) => (
        <Text style={[styles.text, pressed && styles.pressedText]}>
          {pressed ? '누르는 중...' : '눌러보세요'}
        </Text>
      )}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingVertical: 12,
    paddingHorizontal: 24,
    borderRadius: 8,
  },
  pressed: {
    backgroundColor: '#0056CC',
    transform: [{ scale: 0.96 }],
  },
  text: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  pressedText: {
    opacity: 0.8,
  },
});

hitSlop — 터치 영역 확장

작은 버튼의 터치 영역을 시각적 크기보다 크게 만들 수 있습니다.

TSX
// 시각적으로는 작지만 터치 영역은 넓음
<Pressable
  onPress={handlePress}
  hitSlop={20} // 상하좌우 20dp 확장
  // 또는 개별 지정
  // hitSlop={{ top: 10, bottom: 10, left: 20, right: 20 }}
>
  <Text style={{ fontSize: 12 }}>작은 버튼</Text>
</Pressable>

Apple Human Interface Guidelines에서는 터치 타겟을 최소 44x44pt로 권장합니다. hitSlop을 활용하면 디자인을 해치지 않으면서 터치 영역을 확보할 수 있습니다.

android_ripple — Android 리플 효과

TSX
<Pressable
  onPress={handlePress}
  android_ripple={{
    color: 'rgba(0, 0, 0, 0.1)',
    borderless: false,
  }}
  style={styles.button}
>
  <Text>Android 리플 버튼</Text>
</Pressable>

TouchableOpacity

가장 많이 사용되는 터치 컴포넌트입니다. 누르면 투명도가 변하는 시각적 피드백을 제공합니다.

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

function OpacityButton() {
  return (
    <TouchableOpacity
      onPress={() => console.log('탭!')}
      activeOpacity={0.7}  // 누를 때 투명도 (기본: 0.2)
      disabled={false}
      style={styles.button}
    >
      <Text style={styles.text}>투명도 버튼</Text>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#34C759',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  text: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
});

실전 버튼 컴포넌트 만들기

TSX
import { Pressable, Text, ActivityIndicator, StyleSheet } from 'react-native';

interface ButtonProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary' | 'outline';
  loading?: boolean;
  disabled?: boolean;
}

// 재사용 가능한 버튼 컴포넌트
function CustomButton({
  title,
  onPress,
  variant = 'primary',
  loading = false,
  disabled = false,
}: ButtonProps) {
  const isDisabled = disabled || loading;

  return (
    <Pressable
      onPress={onPress}
      disabled={isDisabled}
      style={({ pressed }) => [
        styles.base,
        styles[variant],
        pressed && styles.pressed,
        isDisabled && styles.disabled,
      ]}
    >
      {loading ? (
        <ActivityIndicator color="white" />
      ) : (
        <Text style={[
          styles.text,
          variant === 'outline' && styles.outlineText,
        ]}>
          {title}
        </Text>
      )}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  base: {
    paddingVertical: 14,
    paddingHorizontal: 24,
    borderRadius: 8,
    alignItems: 'center',
    justifyContent: 'center',
    minHeight: 48,
  },
  primary: {
    backgroundColor: '#007AFF',
  },
  secondary: {
    backgroundColor: '#5856D6',
  },
  outline: {
    backgroundColor: 'transparent',
    borderWidth: 1.5,
    borderColor: '#007AFF',
  },
  pressed: {
    opacity: 0.8,
    transform: [{ scale: 0.98 }],
  },
  disabled: {
    opacity: 0.5,
  },
  text: {
    color: 'white',
    fontSize: 16,
    fontWeight: '600',
  },
  outlineText: {
    color: '#007AFF',
  },
});

터치 이벤트 흐름

PLAINTEXT
onPressIn → onPressOut → onPress

    └── (500ms 이상 유지) → onLongPress → onPressOut
TSX
function TouchFlow() {
  return (
    <Pressable
      onPressIn={() => {
        // 손가락이 화면에 닿는 순간
        // 시각적 피드백 시작 (크기 줄이기, 색상 변경 등)
      }}
      onPressOut={() => {
        // 손가락을 떼는 순간
        // 시각적 피드백 해제
      }}
      onPress={() => {
        // 정상적인 탭 완료
        // 실제 액션 실행
      }}
      onLongPress={() => {
        // 길게 누르기 완료
        // 컨텍스트 메뉴, 삭제 확인 등
      }}
    >
      <Text>터치 이벤트 테스트</Text>
    </Pressable>
  );
}

더블탭 구현

React Native에는 기본 더블탭 이벤트가 없어서 직접 구현해야 합니다.

TSX
import { useRef, useCallback } from 'react';
import { Pressable, Text } from 'react-native';

function DoubleTap() {
  const lastTap = useRef<number>(0);

  const handlePress = useCallback(() => {
    const now = Date.now();
    const DOUBLE_TAP_DELAY = 300;

    if (now - lastTap.current < DOUBLE_TAP_DELAY) {
      // 더블탭 감지
      console.log('더블탭!');
    } else {
      // 싱글탭
      console.log('싱글탭');
    }

    lastTap.current = now;
  }, []);

  return (
    <Pressable onPress={handlePress}>
      <Text>더블탭 테스트</Text>
    </Pressable>
  );
}

스크롤 영역 안의 터치 처리

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

function ScrollWithTouchable() {
  return (
    <ScrollView>
      {/* 스크롤 안에서도 터치 이벤트 동작 */}
      {Array.from({ length: 20 }, (_, i) => (
        <Pressable
          key={i}
          onPress={() => console.log(`아이템 ${i} 탭`)}
          style={({ pressed }) => ({
            padding: 16,
            backgroundColor: pressed ? '#f0f0f0' : 'white',
            borderBottomWidth: 1,
            borderBottomColor: '#eee',
          })}
        >
          <Text>아이템 {i}</Text>
        </Pressable>
      ))}
    </ScrollView>
  );
}

정리

  • Pressable을 기본으로 사용 하세요 — 가장 유연하고 최신 API입니다
  • TouchableOpacity도 여전히 많이 사용되며, 간단한 투명도 피드백에 적합합니다
  • hitSlop으로 작은 터치 타겟의 영역을 넓혀 UX를 개선하세요
  • 터치 피드백은 사용자 경험에 직결되므로, 눌림 상태의 시각적 변화를 꼭 넣으세요
  • Android에서는 android_ripple로 플랫폼 네이티브 느낌을 줄 수 있습니다
댓글 로딩 중...