사용자들이 가려고 하거나 관심 있는 나라에 대한 정보를 얻기 위해 나라를 선택하는 시나리오에서 어떤 방식으로 나라를 선택하는 기능을 구현할지 고민했다.
단순히 Grid 형식으로 약 200여 개의 나라 아이템들을 배치하여 하나의 나라를 터치하면 그 나라에 대한 정보를 제공하고, 그리드에 사용될 리스트를 나열할 때 ReactNative의 기본 컴포넌트인 FlatList를 사용했다.
FlatList는 많은 양의 리스트 아이템을 스크롤 형식으로 보여주고자 할 때 사용하는 컴포넌트로, 한 번에 리스트들의 모든 아이템을 렌더링 하지 않고 화면에 보이는 부분만을 렌더링할 때 사용됨.
실제 코드에서 필요한 부분만
import React from 'react';
import { View, FlatList } from 'react-native';
import { Nations } from '../data/dummy-data'; // 약 200여개의 나라 아이템객체 (각 객체는 '나라 이름, iso 코드, 국기 이미지' 의 데이터를 담고있음)
import TouchableTab from './TouchableTab'; // 아이템을 터치하면 해당하는 나라의 정보를 제공하는 화면으로 갈 수 있도록하는 컴포넌트
const NationGrid = props => {
return (
<FlatList
data={Nations} // 리스트 데이터
renderItem={({ item }) => ( // nationList의 각 요소(item)마다 아래 <View/> 형식으로 렌더링함
<View>
<TouchableTab
selectedNation={item}
onSelect={() => props.navigateToInfo(item)} // 정보 제공화면으로 이동
/>
</View>
)}
numColumns={3} // 행 하나에 렌더링할 아이템 개수 3개
/>
)
}
🚨 위 코드로 앱을 빌드했더니 문제가 발생했다!
import { View, FlatList } from 'react-native';
import { SearchBar } from 'react-native-elements'; // 검색기능 라이브러리
import { Nations } from '../data/dummy-data';
import TouchableTab from './TouchableTab';
const SearchComponent = props => {
const [query, setQuery] = useState(''); // 컴포넌트를 업데이트할 트리거로 사용될 query
const [filteredNationList, setFilteredNationList] = useState([]); // 필터링된 리스트로 사용하기위한 filteredNationList
useEffect(() => { // 첫 렌더링 시에만 filteredNationList에 Nations 값을 할당하기 위해 useEffect()를 사용
setFilteredNationList(Nations.slice());
}, []) // [value] 일 경우 'value' 가 업데이트 될 경우에만 useEffect()를 call / [] 처럼 아무것도 없을 시 해당 컴포넌트 최초 렌더링 시에만 call / 필터링을 통해filteredNationList 값이 변경되어 컴포넌트가 업데이트 되더라도 다시setFilteredNationList(Nations.slice()) 가 호출되지 않음
const updateQuery = (text) => {
if (text) { // text 값이 존재할 경우
let nationList = Nations.filter(function (item) {
return item.nation.startsWith(text, 0) === true;
})
setFilteredNationList(nationList);
setQuery(text); // query 값에 text를 할당하여 query 값이 변경되면 컴포넌트가 업데이트됨
} else {
setFilteredNationList(Nations.slice()); // text 값이 공백이면 다시 filteredNationList 에 전체 Nations 정보를 할당
setQuery(text);
}
}
const renderFilteredItem = (itemData) => {
if (!query) return; // query 값이 없으면 아무것도 렌더링 하지 않음
const country = itemData.item;
return (
<TouchableTab
selectedNation={country}
onSelect={() => props.navigateToInfo(country)}
/>
)
}
return (
<View>
<SearchBar
onChangeText={updateQuery} // text 값이 변경될 때마다 call updateQuery()
value={query}
/>
<FlatList
data={filteredNationList} // 필터링된 아이템만을 보여줌
renderItem={renderFilteredItem} // 아이템마다 renderFilteredItem() 의 절차를 따라 렌더링
/>
</View>
)
}
예를 들어 검색창에 '아' 를 작성한다면, '' -> 'ㅇ' -> '아' 로 총 2번 업데이트된다. 처음엔 공백이라 query가 Falsy 값을 가지므로 아무것도 렌더링 되지 않는다.
다음엔 'ㅇ' 로 시작하는 나라 이름은 없기에 필터링을 통해 빈 리스트가 filteredNationList 에 할당되므로 역시 아무것도 렌더링 되지 않는다.
마지막으로 '아' 로 시작하는 나라 이름을 필터링하면 [아프가니스탄, 아랍에미리트, 아르헨티나, 아루바, 아이티, 아르메니아, 아제르바이잔, 아이슬란드, 아일랜드]가 filtereddNationList 에 할당되어 총 9개의 아이템이 FlatList를 통해 렌더링 된다.
검색 text로 필터링 된 리스트들만을 렌더링하기에 FlatList로 생성할 item들의 수가 크게 줄어들어 도돌이표처럼 반복해서 아이템을 생성하는 문제는 더 이상 발생하지 않았다.
컴포넌트를 렌더링하는 시간과 스크롤의 버벅임이 개선되었다.
개발 도중 문제가 발생했을 때 문제에 대한 접근 방법을 달리하여 문제가 해결되는 경우도 존재함
내가 사용했던 리스트 데이터는 전 세계 나라에 대한 데이터로 약 200여 개의 정적인 데이터였고, 필터링으로 많은 데이터가 걸러졌기에 위처럼 해결할 수 있었음
하지만 필터링을 해도 수백 가지가 되는 데이터를 FlatList로 렌더링 해야 했다면 FlatList의 Attributes를 제대로 숙지하는 것이 필요할 듯
"react": "17.0.1"
"react-native": "0.64.0"