ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [RN 강의] Mission: 게시글의 주제(topic) 선정 기능
    강의노트/React Native 2025. 9. 7. 20:36

     

    제로초님의 React Native Threads Clone 강의 2주차 Section2의 Mission이다.

     

    1. 기능 구현 결과

    게시글의 주제 추가 기능

     

    2. 주제(해시태그) 추가 기능 분석

    React Native 로 구현된 게시글 작성 모달에 주제(해시태그)를 추가하는 기능을 추가하여 내용 정리를 한다. Threads 앱의 클론 코딩을 수행했다.

     

    🛠️ 사용된 주요 기술과 라이브러리

    1. React Native Core

    사용된 대부분의 컴포넌트들은 React Native에서 제공하는 것을 사용했다. 특히 FlatList는 스레드 목록과 토픽 옵션을 효율적으로 렌더링 하는데 사용하였다. Viewport 상에서 안보이는 건 렌더링을 미뤄두는 최적화가 알아서 된다.

    import { FlatList, Image, Pressable, Text, TextInput, TouchableOpacity, View } from 'react-native';

     

    2. React Native Gesture Handler

    주제(해시태그)를 선정할 때 사용되는 바텀시트 제어를 위해 사용된 라이브러리이다. 사용자의 아래 방향 드래그 제스처를 통해서 바텀시트를 닫을 수 있다.

    import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';

     

    3. React Native Reanimated

    바텀시트 열기/닫기 애니메이션에 부드러운 애니메이션 처리을 넣기 위해 사용했다.

    import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';

     

    4. React Native Safe Area Context

    모바일의 상단의 노치나 하단의 홈 인디케이터 영역을 고려하여, 사용자의 모바일을 감지해서 적정 여백(insets)을 알려주는 편리한 라이브러리이다.

    import { useSafeAreaInsets } from 'react-native-safe-area-context';

     

    🎯 주제(해시태그) 추가 기능 상세 분석

    1. 상태관리

    const [isTopicDrawerVisible, setIsTopicDrawerVisible] = useState(false);
    const [currentTopic, setCurrentTopic] = useState<string | null>(null);
    const [searchQuery, setSearchQuery] = useState('');

     

    2. 바텀시트 애니메이션

    Gesture 라이브러리를 사용하면서 바텀시트를 닫는 closeTopicDrawer 함수를 실행하지 못해서 Error를 만났다. React Native Reanimated는 성능 최적화를 위해 UI 스레드에서 애니메이션을 처리한다. 하지만 JavaScript 함수들은 JavaScript 스레드에서 실행되어야 했기 때문이다. closeTopicDrawer 함수를 JavaScript 스레드에서 실행될 수 있도록 runOnJS()를 통해서 사용했다. 참고로 UI 스레드에서는 드래그 중에 translateY.value 와 sheetHeight.value 업데이트를 하고, 사용자의 드래그로 바텀시트의 translateY가 34 pixel (바텀시트 헤드 height)을 넘어가면 드래그를 종료하고 바텀시트를 닫는다.

    • useSharedValue: 애니메이션 값을 저장하는 Reanimated의 핵심
    • Gesture.Pan(): 드래그 제스처를 감지
    • withSpring(): 스프링 애니메이션으로 부드러운 움직임
    • runOnJS(): UI 스레드에서 JavaScript 함수 실행
    // 드래그 애니메이션을 위한 shared values
    const translateY = useSharedValue(0);
    const sheetHeight = useSharedValue(1);
    
    // 드래그 제스처 정의
    const panGesture = useMemo(() => {
      return Gesture.Pan()
        .onUpdate((event: any) => {
          // 드래그 중 실시간으로 호출
          const progress = Math.min(event.translationY / 200, 1);
          translateY.value = event.translationY;
          sheetHeight.value = 1 - progress * 0.3;
        })
        .onEnd((event: any) => {
          // 아래 드래그로 34px 이상 드래그 시 바텀시트 닫기
          if (event.translationY > 34) {
            runOnJS(closeTopicDrawer)();
          } else {
            // 드래그가 임계값 이하면 원래 위치로 복원
            translateY.value = withSpring(0);
            sheetHeight.value = withSpring(1);
          }
        });
    }, [closeTopicDrawer, translateY, sheetHeight]);

     

    Gesture.Pan()의 의미

    Pan(팬)의 의미는 드래그(drag) 또는 끌어당기기(pull) 동작을 의미한다.

    • Panning: 카메라나 지도에서 화면을 드래그하여 이동하는 동작
    • Pan gesture: 사용자가 화면을 터치하고 드래그하는 제스쳐
    // 다양한 제스처 타입들
    Gesture.Tap()     // 탭 (한 번 터치)
    Gesture.LongPress() // 롱프레스 (길게 누르기)
    Gesture.Pinch()   // 핀치 (두 손가락으로 확대/축소)
    Gesture.Rotation() // 회전 (두 손가락으로 회전)
    Gesture.Pan()     // 팬 (드래그/끌어당기기)

     

    Pan 제스처 event 객체의 속성들

    event.translationY  // Y축으로 이동한 거리 (픽셀)
    event.translationX  // X축으로 이동한 거리 (픽셀)
    event.velocityY     // Y축 이동 속도
    event.velocityX     // X축 이동 속도
    event.absoluteX     // 절대 X 좌표
    event.absoluteY     // 절대 Y 좌표

     

     

    3. 주제 선택 로직

    모든 스레드에 동일한 주제를 적용하기 위한 이벤트 핸들러이다. 상태 업데이트 후에는 바텀시트가 자동으로 닫는다.

    • useCallback: 함수 메모이제이션으로 성능 최적화
    const handleTopicSelect = useCallback((topic: string) => {
      // 모든 스레드에 동일한 토픽 적용
      setCurrentTopic(topic);
      setThreads((prevThreads) => {
        const updatedThreads = prevThreads.map((thread) => ({
          ...thread,
          topic,
        }));
        return updatedThreads;
      });
      closeTopicDrawer();
    }, [closeTopicDrawer, topicOptions]);

     

    4. 검색 기능

    실시간 검색 필터링 기능을 구현하기 위한 조건문이다. 수동으로 주제가되는 항목들을 topicOptions에 넣어두고 일치하는 항목이 없는 경우, 새로운 주제를 추가하도록 했다.

      const topicOptions = useMemo(
        () => [
          '창업아이디어',
          '하이에나',
          '비행기표',
          'AI',
          '강의',
          '인프런',
          '운동하는직장인',
          'Threads birthday',
        ],
        []
      );
    
    const filteredTopics = topicOptions.filter((topic) =>
      topic.toLowerCase().includes(searchQuery.toLowerCase())
    );
    
    // 검색어가 있고, 정확히 일치하는 항목이 없는 경우 새로운 주제 추가 옵션 표시
    if (
      searchQuery.trim().length > 0 &&
      !topicOptions.some((topic) =>
        topic.toLowerCase() === searchQuery.toLowerCase()
      )
    ) {
      return [...filteredTopics, searchQuery.trim()];
    }

     

    💄 UI/UX 디자인 패턴

    1. 바텀시트 구조

    {isTopicDrawerVisible && (
      <View style={styles.bottomSheetOverlay}>
        <Animated.View style={[styles.bottomSheetContainer, animatedSheetStyle]}>
          <GestureDetector gesture={panGesture}>
            <View style={styles.bottomSheetHeader}>
              <View style={styles.bottomSheetHeaderLine} />
            </View>
          </GestureDetector>
          {/* 검색 바 */}
          {/* 현재 토픽 표시 */}
          {/* 토픽 옵션 리스트 */}
        </Animated.View>
      </View>
    )}

     

    2. 주제 표시 UI

    주제가 없을 때는 첫번째 스레드에만 오른쪽 꺾쇠(>) 아이콘과 palceholder를 보이도록 했다. 스레드가 새로 추가될 때 주제는 당연히 null 상태이므로 아무것도 보이지 않다가, 첫번쨰 스레드에 주제가 추가되면 모든 스레드의 주제가 아이콘과 함께 보이게 된다.

    {item.topic ? (
      <View style={styles.topicInputContainer}>
        <Ionicons name="chevron-forward" size={16} color="#999" />
        <Pressable onPress={openTopicDrawer}>
          <Text style={styles.topicText}>{item.topic}</Text>
        </Pressable>
      </View>
    ) : (
      index === 0 && (
        <View style={styles.topicInputContainer}>
          <Ionicons name="chevron-forward" size={16} color="#999" />
          <TextInput
            style={styles.topicPlaceholder}
            placeholder="Add to topic"
            placeholderTextColor="#999"
            onFocus={openTopicDrawer}
            editable={false}
          />
        </View>
      )
    )}

     

    .

    .

    .

    .

    .

    .

    .

    코드 저장소

    https://github.com/redcontroller/threads-clone/blob/main/app/modal.tsx

     

    threads-clone/app/modal.tsx at main · redcontroller/threads-clone

    제로초 React native 학습 레포. Contribute to redcontroller/threads-clone development by creating an account on GitHub.

    github.com

     

Designed by Tistory.