효과적인 필터링 시스템 구현하기: React Native에서 사용자 맞춤형 데이터 필터링

oversleep·2025년 2월 25일
0

app-development

목록 보기
23/38
post-thumbnail

들어가며

모바일 앱에서 사용자가 원하는 정보를 빠르게 찾을 수 있도록 도와주는 핵심 기능 중 하나는 필터링입니다.
특히 데이터가 많은 앱에서는 효율적인 필터링 시스템이 사용자 경험을 크게 향상시킬 수 있습니다.
이 글에서는 React Native에서 효과적인 필터링 시스템을 구현하는 방법에 대해 알아보겠습니다.

필터링 시스템의 중요성

효과적인 필터링 시스템은 다음과 같은 이점을 제공합니다:

  1. 향상된 사용자 경험: 사용자가 원하는 정보를 빠르게 찾을 수 있게 합니다.
  2. 데이터 탐색 용이성: 복잡하고 방대한 데이터를 쉽게 탐색할 수 있습니다.
  3. 개인화된 콘텐츠: 사용자의 선호도나 요구에 맞는 콘텐츠를 제공할 수 있습니다.
  4. 서버 부하 감소: 클라이언트 측 필터링을 통해 서버 요청을 줄일 수 있습니다.

필터링 구현 방식: 서버 측 vs 클라이언트 측

필터링을 구현하는 방식은 크게 서버 측과 클라이언트 측으로 나눌 수 있습니다:

서버 측 필터링

const fetchFilteredData = async (filters) => {
  try {
    setLoading(true);
    const response = await api.get("/data", {
      params: {
        region: filters.region !== "all" ? filters.region : undefined,
        level: filters.level !== "all" ? filters.level : undefined,
        // 기타 필터 파라미터
      },
    });
    
    setData(response.data.items);
  } catch (error) {
    console.error("Error fetching filtered data:", error);
  } finally {
    setLoading(false);
  }
};

장점:

  • 대용량 데이터 처리에 효율적
  • 클라이언트 리소스 절약
  • 백엔드 데이터베이스의 인덱싱 활용 가능

단점:

  • 필터 변경마다 네트워크 요청 발생
  • 서버 부하 증가
  • 오프라인 사용 불가

클라이언트 측 필터링

const applyFilters = (data) => {
  let filteredData = [...data];
  
  // 지역 필터 적용
  if (regionFilter !== "all") {
    // 정규식을 사용한 유연한 필터링
    const regionName = regionFilter.replace(/서울특별시\s|서울시\s/, "");
    filteredData = filteredData.filter((item) => {
      const itemRegion = item.region.replace(/서울특별시\s|서울시\s/, "");
      return itemRegion.includes(regionName) || regionName.includes(itemRegion);
    });
  }
  
  // 레벨 필터 적용
  if (levelFilter !== "all") {
    filteredData = filteredData.filter((item) => item.level === levelFilter);
  }
  
  return filteredData;
};

장점:

  • 빠른 응답 시간
  • 네트워크 요청 감소
  • 오프라인에서도 사용 가능
  • 더 유연한 필터링 로직 구현 가능

단점:

  • 대용량 데이터 처리 시 성능 이슈
  • 클라이언트 메모리 사용량 증가
  • 초기 데이터 로드 시간 증가

React Native에서 효과적인 필터링 시스템 구현하기

1. 필터 상태 관리

필터링을 위한 상태 변수를 설정합니다:

const [allData, setAllData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [regionFilter, setRegionFilter] = useState("all");
const [levelFilter, setLevelFilter] = useState("all");

2. 필터 적용 함수 구현

사용자가 선택한 필터에 따라 데이터를 필터링하는 함수를 구현합니다:

const applyFilters = (data) => {
  let result = [...data];
  
  // 지역 필터 적용
  if (regionFilter !== "all") {
    // 서울특별시, 서울시 등을 제거하고 구 이름만으로 비교
    const regionName = regionFilter.replace(/서울특별시\s|서울시\s/, "");
    result = result.filter((item) => {
      const itemRegion = item.region.replace(/서울특별시\s|서울시\s/, "");
      return itemRegion.includes(regionName) || regionName.includes(itemRegion);
    });
  }
  
  // 레벨 필터 적용
  if (levelFilter !== "all") {
    result = result.filter((item) => item.level === levelFilter);
  }
  
  setFilteredData(result);
};

3. 필터 변경 시 적용

사용자가 필터를 변경할 때마다 필터링을 다시 적용합니다:

useEffect(() => {
  applyFilters(allData);
}, [regionFilter, levelFilter]);

4. 필터 선택 UI 구현

사용자가 필터를 선택할 수 있는 UI를 구현합니다:

const FilterSection = ({
  regionFilter,
  levelFilter,
  setRegionFilter,
  setLevelFilter,
  data
}) => {
  // 고유한 지역 목록 생성
  const uniqueRegions = Array.from(
    new Set(data.map((item) => item.region))
  ).filter(Boolean);
  
  // 지역 옵션 생성
  const regionOptions = [
    { label: "전체 지역", value: "all" },
    ...uniqueRegions.map((region) => ({
      label: region.includes("서울특별시 ")
        ? region.replace("서울특별시 ", "")
        : region,
      value: region,
    })),
  ];
  
  // 레벨 옵션 생성
  const levelOptions = [
    { label: "전체 레벨", value: "all" },
    { label: "초급", value: "BEGINNER" },
    { label: "중급", value: "INTERMEDIATE" },
    { label: "상급", value: "ADVANCED" },
  ];

  return (
    <View style={styles.filterContainer}>
      <Picker
        selectedValue={regionFilter}
        onValueChange={setRegionFilter}
        style={styles.picker}
      >
        {regionOptions.map((option) => (
          <Picker.Item 
            key={option.value} 
            label={option.label} 
            value={option.value} 
          />
        ))}
      </Picker>

      <Picker
        selectedValue={levelFilter}
        onValueChange={setLevelFilter}
        style={styles.picker}
      >
        {levelOptions.map((option) => (
          <Picker.Item 
            key={option.value} 
            label={option.label} 
            value={option.value} 
          />
        ))}
      </Picker>
    </View>
  );
};

필터링 최적화 전략

1. 메모이제이션 활용

React의 useMemo를 사용하여 필터링 결과를 메모이제이션할 수 있습니다:

const filteredData = useMemo(() => {
  let result = [...allData];
  
  // 필터링 로직...
  
  return result;
}, [allData, regionFilter, levelFilter]);

2. 필터링 로직 개선

데이터 구조에 따라 필터링 로직을 최적화할 수 있습니다:

// 객체를 사용한 빠른 조회
const levelMap = {
  "BEGINNER": true,
  "INTERMEDIATE": true,
};

// O(n) 시간 복잡도
const filteredByLevel = data.filter(item => levelMap[item.level]);

3. 점진적 필터링

여러 필터를 한 번에 적용하는 대신 단계적으로 적용하여 성능을 개선할 수 있습니다:

let result = [...allData];

if (regionFilter !== "all") {
  result = result.filter(/* 지역 필터링 */);
}

if (levelFilter !== "all") {
  result = result.filter(/* 레벨 필터링 */);
}

// 추가 필터...

4. 데이터 인덱싱

대량의 데이터를 처리할 때는 검색 효율을 높이기 위해 인덱싱을 고려할 수 있습니다:

// 데이터 로드 시 인덱스 생성
const createIndex = (data) => {
  const regionIndex = {};
  const levelIndex = {};
  
  data.forEach((item, idx) => {
    // 지역 인덱스
    if (!regionIndex[item.region]) {
      regionIndex[item.region] = [];
    }
    regionIndex[item.region].push(idx);
    
    // 레벨 인덱스
    if (!levelIndex[item.level]) {
      levelIndex[item.level] = [];
    }
    levelIndex[item.level].push(idx);
  });
  
  return { regionIndex, levelIndex };
};

// 인덱스를 사용한 필터링
const filterWithIndex = (data, indexes, filters) => {
  let candidateIndices = null;
  
  if (filters.region !== "all") {
    candidateIndices = new Set(indexes.regionIndex[filters.region] || []);
  }
  
  if (filters.level !== "all") {
    const levelIndices = new Set(indexes.levelIndex[filters.level] || []);
    
    if (candidateIndices) {
      // 교집합 계산
      candidateIndices = new Set(
        [...candidateIndices].filter(idx => levelIndices.has(idx))
      );
    } else {
      candidateIndices = levelIndices;
    }
  }
  
  if (!candidateIndices) {
    return [...data];
  }
  
  return [...candidateIndices].map(idx => data[idx]);
};

문자열 처리와 관련된 특별한 고려사항

필터링 시스템을 구현할 때 문자열 처리와 관련하여 몇 가지 고려해야 할 사항이 있습니다:

1. 대소문자 처리

대소문자를 구분하지 않는 검색을 위해 문자열을 소문자로 변환할 수 있습니다:

const searchTerm = userInput.toLowerCase();
const filteredItems = items.filter(item => 
  item.name.toLowerCase().includes(searchTerm)
);

2. 정규식 활용

복잡한 패턴 매칭을 위해 정규식을 활용할 수 있습니다:

// 서울특별시 또는 서울시로 시작하는 문자열에서 구 이름만 추출
const extractDistrict = (address) => {
  const match = address.match(/(?:서울특별시|서울시)\s+([^\s]+)/);
  return match ? match[1] : address;
};

3. 유사도 검색

사용자가 완벽하게 일치하지 않는 검색어를 입력해도 관련 결과를 찾을 수 있도록 유사도 검색을 구현할 수 있습니다:

// 레벤슈타인 거리를 사용한 유사도 계산
const levenshteinDistance = (a, b) => {
  // 레벤슈타인 알고리즘 구현...
};

// 유사도에 기반한 필터링
const similarityFilter = (items, searchTerm, threshold = 0.8) => {
  return items.filter(item => {
    const similarity = 1 - levenshteinDistance(item.name, searchTerm) / Math.max(item.name.length, searchTerm.length);
    return similarity >= threshold;
profile
궁금한 것, 했던 것, 시행착오 그리고 기억하고 싶은 것들을 기록합니다.

0개의 댓글