
모바일 앱에서 사용자가 원하는 정보를 빠르게 찾을 수 있도록 도와주는 핵심 기능 중 하나는 필터링입니다.
특히 데이터가 많은 앱에서는 효율적인 필터링 시스템이 사용자 경험을 크게 향상시킬 수 있습니다.
이 글에서는 React Native에서 효과적인 필터링 시스템을 구현하는 방법에 대해 알아보겠습니다.
효과적인 필터링 시스템은 다음과 같은 이점을 제공합니다:
필터링을 구현하는 방식은 크게 서버 측과 클라이언트 측으로 나눌 수 있습니다:
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;
};
장점:
단점:
필터링을 위한 상태 변수를 설정합니다:
const [allData, setAllData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [regionFilter, setRegionFilter] = useState("all");
const [levelFilter, setLevelFilter] = useState("all");
사용자가 선택한 필터에 따라 데이터를 필터링하는 함수를 구현합니다:
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);
};
사용자가 필터를 변경할 때마다 필터링을 다시 적용합니다:
useEffect(() => {
applyFilters(allData);
}, [regionFilter, levelFilter]);
사용자가 필터를 선택할 수 있는 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>
);
};
React의 useMemo를 사용하여 필터링 결과를 메모이제이션할 수 있습니다:
const filteredData = useMemo(() => {
let result = [...allData];
// 필터링 로직...
return result;
}, [allData, regionFilter, levelFilter]);
데이터 구조에 따라 필터링 로직을 최적화할 수 있습니다:
// 객체를 사용한 빠른 조회
const levelMap = {
"BEGINNER": true,
"INTERMEDIATE": true,
};
// O(n) 시간 복잡도
const filteredByLevel = data.filter(item => levelMap[item.level]);
여러 필터를 한 번에 적용하는 대신 단계적으로 적용하여 성능을 개선할 수 있습니다:
let result = [...allData];
if (regionFilter !== "all") {
result = result.filter(/* 지역 필터링 */);
}
if (levelFilter !== "all") {
result = result.filter(/* 레벨 필터링 */);
}
// 추가 필터...
대량의 데이터를 처리할 때는 검색 효율을 높이기 위해 인덱싱을 고려할 수 있습니다:
// 데이터 로드 시 인덱스 생성
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]);
};
필터링 시스템을 구현할 때 문자열 처리와 관련하여 몇 가지 고려해야 할 사항이 있습니다:
대소문자를 구분하지 않는 검색을 위해 문자열을 소문자로 변환할 수 있습니다:
const searchTerm = userInput.toLowerCase();
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(searchTerm)
);
복잡한 패턴 매칭을 위해 정규식을 활용할 수 있습니다:
// 서울특별시 또는 서울시로 시작하는 문자열에서 구 이름만 추출
const extractDistrict = (address) => {
const match = address.match(/(?:서울특별시|서울시)\s+([^\s]+)/);
return match ? match[1] : address;
};
사용자가 완벽하게 일치하지 않는 검색어를 입력해도 관련 결과를 찾을 수 있도록 유사도 검색을 구현할 수 있습니다:
// 레벤슈타인 거리를 사용한 유사도 계산
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;