카테고리 없음

[React-native] 검색기능 구현1 (UI 구현)

성코 2024. 6. 20. 17:56

❏ 기능개선 개요

- 기기가 많이 등록되어 있는 계정에서 기기 찾기가 어렵다는 요구사항이 있어 기능개선을 위해 작업.

검색 기능 구현을 하기 위해 전체 리스트를 서버에서 처음 한번만 받은 후, 클라이언트 단에서 텍스트 입력시에 필터링하는 방법을 사용했다. 

 

결과적으론 업데이트에 반영되진 못했다...

테스트 과정에서 대규모 트래픽에서 실제로 기능개선을 하긴 어렵다는 판단에 퇴사 전 마지막 배포에 반영하지 못헀다.

요구사항에 적합한 기능개선 방법은 페이징처리도 함께 이루어지는게 필요했다. 팀과 상부와의 소통의 중요성을 느끼게 되는 계기가 되었다 .. 

 

 구현작업을 위한 프로젝트 설명
- 기기의 상태와 등록기기가 보이는 메인 화면을 담당하는 Homeview (상위컴포넌트)가 있다.

최종적으로 검색결과가 반영되어야 한다. 
- 하위컴포넌트에는 Head 영역을 담당하는 Head 컴포넌트와
내가 추가할 검색기능을 담당하는 HeadSearchBox로 이루어져 있다.
- 참고로 기존 프로젝트에 기능을 추가하는 거여서 ir 제어를 하는 HeadIrBox도 존재한다.

(검색기능박스와 동시에 열리면 안됨)

상위컴포넌트에서는 상태관리와 로직처리를 해야하고, 하위컴포넌트에서는 단순 UI작업과 검색어 입력처리가 필요하다. 

 검색기능 UI 구현과정
1. Head 영역에 들어가는 검색아이콘 추가한다
2. 검색아이콘 클릭시 toggle 박스 open / close 되도록
3. toggle open시 검색박스 UI와 검색어 입력이 가능한 컴포넌트를 생성 (HeadSearchBox)

1. Head 영역에 들어갈 검색 아이콘 추가하기

https://oblador.github.io/react-native-vector-icons/

 

아이콘 사용을 위해 아이콘이 사용되는 Head.js에 아이콘을 import한다. 

import Feather from 'react-native-vector-icons/Feather';

 

Head에 검색 아이콘 추가한 UI

 

{props.searchControl && (
  <TouchableOpacity onPress={props.toggleSearchOpen}> //클릭시 toggle open되도록
    {isTabScale() ? ( //해상도에 따른 size 분기처리
      <Feather
        name="search" //가져오고 싶은 아이콘명을 넣어준다
        size={responsiveWidth(5)}
        color={colors.BlackGrey}
      />
    ) : (
      <Feather name="search" size={30} color={colors.BlackGrey} />
    )}
  </TouchableOpacity>
)}

 

2. Head에는 기본값이 검색아이콘만 보이고 검색 box는 아이콘을 클릭해야 보이기 때문에, 상위컴포넌트에서는 클릭여부를 판단하는 toggleSearchOpen 값을 Head 컴포넌트에 전달


2-1. 생성자 생성

class AirdeepHomeView extends Component {
  constructor(props) {
    // 생성자
    super(props);
    this.focus = React.createRef();
    this.state = {
      appState: AppState.currentState,
      basis: RNLocalize.getCountry() === 'KR' ? 'kor' : 'who',
      refreshVisible: false,
      modalVisible: true,
      isAutoRefresh: false,
      isLoaded: false,
      isError: false,
      isRefreshing: false,
      isIrOpen: false,
      isSearchOpen: false, //HeadSearchBox
    };
  }

2-2. 기존 ir제어버튼과 toggle 동시에 open되지 않도록 setState 수정

  toggleOpen = () => {
    this.setState({isIrOpen: !this.state.isIrOpen, isSearchOpen: false});
  };

  toggleSearchOpen = () => {
    this.setState({isSearchOpen: !this.state.isSearchOpen, isIrOpen: false});
  };

2-3. 하위컴포넌트 Head에 toggleSearchOpen 값 전달

<Head
  irControl={true}
  searchControl={true}
  toggleOpen={this.toggleOpen}
  toggleSearchOpen={this.toggleSearchOpen}
/>

3. 검색박스 UI와 입력처리하는 하위 컴포넌트 HeadSearchBox 생성

검색 아이콘 클릭시 box 오픈

export const HeadSearchBox = ({expanded, search, handleSearch, handleDelete}) => {
  const {t} = useTranslation();
  const [height, setHeight] = useState(0);
  const animatedHeight = useSharedValue(0);

  const onLayout = event => {
    const onLayoutHeight = event.nativeEvent.layout.height;
    if (onLayoutHeight > 0 && height !== onLayoutHeight) {
      setHeight(onLayoutHeight);
    }
  };

  const collapsableStyle = useAnimatedStyle(() => {
    animatedHeight.value = expanded ? withTiming(height) : withTiming(0);
    return {
      height: animatedHeight.value,
    };
  }, [expanded, height]);

  return (
    <Animated.View style={[collapsableStyle, {overflow: 'hidden'}]}>
      <View w="full" style={{position: 'absolute', top: 0}} onLayout={onLayout}>
        <HStack
          w="full"
          h={isTabScale() ? '24' : '10'}
          bgColor={colors.White}
          borderBottomRadius="3xl"
          paddingX="9%"
          alignItems="center"
          justifyContent="center"
          space={1}>
          <>
            <View
              name="label"
              h="80%"
              justifyContent="center"
              flex={1}
              marginBottom={2}>
              <TextInput
                style={styles.input}
                placeholder={t('기기명 입력')}
                value={search}
                onChangeText={handleSearch}
              />
            </View>
            <View
              name="delete"
              h="80%"
              justifyContent="center"
              marginBottom={2}>
              <Pressable onPress={handleDelete}>
                {({isPressed}) => (
                  <Center
                    w={responsiveFontSize(5)}
                    h={responsiveFontSize(5)}
                    borderRadius="md">
                    <Octicons
                      name="x-circle-fill"
                      size={20}
                      color={
                        isPressed ? colors.BlackGrey : colors.LightBlackGrey
                      }
                    />
                  </Center>
                )}
              </Pressable>
            </View>
          </>
        </HStack>
      </View>
    </Animated.View>
  );
};