๐Ÿงญ 71. React Native์—์„œ ์•ฑ ๋‚ด ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์ „๋žต โ€” ์ธํ’‹ ๋””๋ฐ”์šด์‹ฑ, ํ•„ํ„ฐ๋ง, ์„น์…˜ ๋ฆฌ์ŠคํŠธ๊นŒ์ง€

JM_Devยท2025๋…„ 8์›” 7์ผ
0
post-thumbnail

์•ฑ์„ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ์œ ์ € ๋ฆฌ์ŠคํŠธ, ์ƒํ’ˆ ๋ชฉ๋ก, ๊ฒŒ์‹œ๊ธ€ ๋“ฑ์—์„œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ๊ผญ ํ•„์š”ํ•ด์ง„๋‹ค.
์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํžˆ TextInput ํ•˜๋‚˜ ๋„ฃ๊ณ  includes()๋กœ ํ•„ํ„ฐ๋ง๋งŒ ํ•˜๋ฉด ๋  ์ค„ ์•Œ์•˜๋Š”๋ฐ,
๋ง‰์ƒ ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ๊ฒ€์ƒ‰์€ ์ƒ๊ฐ๋ณด๋‹ค ์‹ ๊ฒฝ ์จ์•ผ ํ•  ํฌ์ธํŠธ๊ฐ€ ๋งŽ์•˜๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ๊ฒช์—ˆ๋˜
๊ฒ€์ƒ‰ ์ธํ’‹ ์ฒ˜๋ฆฌ, ๋””๋ฐ”์šด์‹ฑ, ํ•„ํ„ฐ๋ง, ์„น์…˜ ๋ฆฌ์ŠคํŠธ, ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด ์ €์žฅ๊นŒ์ง€์˜ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ณธ๋‹ค.


โœ… ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ์ค‘์š”ํ•œ ์ด์œ 

ํ•ญ๋ชฉ์„ค๋ช…
UX ํ–ฅ์ƒ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด์•ผ ์•ฑ ๋งŒ์กฑ๋„๊ฐ€ ์˜ฌ๋ผ๊ฐ„๋‹ค
๋ฐ์ดํ„ฐ ๋งŽ์„์ˆ˜๋ก ํ•„์ˆ˜์ˆ˜์‹ญ ๊ฐœ ์ด์ƒ์˜ ๋ฆฌ์ŠคํŠธ์—์„œ๋Š” ๊ฒ€์ƒ‰ ์—†์ด๋Š” ํƒ์ƒ‰์ด ์–ด๋ ต๋‹ค
์‹ค์‹œ๊ฐ„ ๋ฐ˜์‘๊ฒ€์ƒ‰์–ด์— ๋”ฐ๋ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒฝํ—˜์ด ์ค‘์š”ํ•˜๋‹ค

๐Ÿ” ๊ธฐ๋ณธ ๊ฒ€์ƒ‰ ๊ตฌํ˜„ (TextInput + filter)

const [query, setQuery] = useState('');
const [filteredData, setFilteredData] = useState(data);

useEffect(() => {
  const newData = data.filter(item => item.name.includes(query));
  setFilteredData(newData);
}, [query]);
<TextInput
  value={query}
  onChangeText={setQuery}
  placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"
/>
  • ์ด๊ฑด ์ •๋ง ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์‹์ธ๋ฐ, ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ”๋กœ ํ•„ํ„ฐ๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์—
    ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์„ฑ๋Šฅ ์ด์Šˆ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

โณ ๋””๋ฐ”์šด์‹ฑ ์ฒ˜๋ฆฌ๋กœ ์ตœ์ ํ™”

์ž…๋ ฅ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ•„ํ„ฐ๋งํ•˜๋ฉด ๋„ˆ๋ฌด ์ž์ฃผ ์—ฐ์‚ฐ์ด ๋ฐœ์ƒํ•œ๋‹ค.
๊ทธ๋ž˜์„œ ๋””๋ฐ”์šด์‹ฑ์„ ํ†ตํ•ด ์ž…๋ ฅ์ด ์ž ์‹œ ๋ฉˆ์ท„์„ ๋•Œ๋งŒ ํ•„ํ„ฐ๋งํ•˜๋„๋ก ๋งŒ๋“ค์—ˆ๋‹ค.

๋””๋ฐ”์šด์Šค ํ›… ๋งŒ๋“ค๊ธฐ

function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

์ ์šฉ

const debouncedQuery = useDebounce(query, 300);

useEffect(() => {
  const newData = data.filter(item => item.name.includes(debouncedQuery));
  setFilteredData(newData);
}, [debouncedQuery]);
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฒ€์ƒ‰์–ด๊ฐ€ ๋ฉˆ์ท„์„ ๋•Œ๋งŒ ํ•„ํ„ฐ๋ง์ด ์ผ์–ด๋‚˜์„œ ์„ฑ๋Šฅ์ด ์ข‹์•„์กŒ๋‹ค.

๐Ÿ“ฆ ์„น์…˜ ๋ฆฌ์ŠคํŠธ๋กœ ์ •๋ฆฌ๋œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋งŒ๋“ค๊ธฐ

๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ์€ ๊ฒฝ์šฐ, ์˜ˆ๋ฅผ ๋“ค์–ด ์œ ์ € ์ด๋ฆ„์ด ๊ฐ€๋‚˜๋‹ค์ˆœ์œผ๋กœ ๋˜์–ด ์žˆ๊ฑฐ๋‚˜
๋‚ ์งœ ๊ธฐ์ค€์œผ๋กœ ๋‚˜๋‰œ๋‹ค๋ฉด SectionList๊ฐ€ ๊ฝค ์œ ์šฉํ•˜๋‹ค.

<SectionList
  sections={filteredSections}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <Text>{item.name}</Text>}
  renderSectionHeader={({ section }) => <Text>{section.title}</Text>}
/>
  • ์„น์…˜๋ณ„๋กœ ์ •๋ฆฌํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ ๋” ๋ณด๊ธฐ ์ข‹์•„์ง€๊ณ , UX๋„ ํ–ฅ์ƒ๋œ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋Š” ์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ•œ๋‹ค:
const filteredSections = [
  {
    title: 'A',
    data: [{ id: '1', name: 'Alex' }, { id: '2', name: 'Amber' }],
  },
  {
    title: 'B',
    data: [{ id: '3', name: 'Ben' }],
  },
];

๐Ÿ” ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด ์ €์žฅ (AsyncStorage)

์‚ฌ์šฉ์ž๊ฐ€ ๊ฒ€์ƒ‰ํ–ˆ๋˜ ๋‚ด์šฉ์„ ์ €์žฅํ•ด๋‘๋ฉด ํŽธ์˜์„ฑ์ด ์˜ฌ๋ผ๊ฐ„๋‹ค.
React Native์—์„  AsyncStorage๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import AsyncStorage from '@react-native-async-storage/async-storage';

const saveSearch = async (keyword: string) => {
  const prev = await AsyncStorage.getItem('recent_keywords');
  const parsed = prev ? JSON.parse(prev) : [];
  const updated = [keyword, ...parsed.filter(k => k !== keyword)].slice(0, 10);
  await AsyncStorage.setItem('recent_keywords', JSON.stringify(updated));
};
  • ์ตœ๋Œ€ 10๊ฐœ๊นŒ์ง€ ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด ์ €์žฅ
  • ์ค‘๋ณต ๋ฐฉ์ง€, ์ตœ์‹  ์ˆœ ์ •๋ ฌ

๊ฒ€์ƒ‰ ์‹คํ–‰ ์‹œ saveSearch(query)๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.


๐Ÿง  ์‹ค์ „์—์„œ ๋А๋‚€ ์ 

์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ๋ฌธ์ž์—ด ํฌํ•จ ์—ฌ๋ถ€๋งŒ ์ฒดํฌํ–ˆ๋Š”๋ฐ,

  • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋Šฆ๊ฒŒ ๋œจ๊ฑฐ๋‚˜
  • ์—ฐ์† ์ž…๋ ฅ ์‹œ ๋ ‰์ด ์ƒ๊ธฐ๊ณ 
  • ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด๊ฐ€ ์•ˆ ๋‚จ์•„์„œ ๋ถˆํŽธํ•˜๋‹ค๋Š” ํ”ผ๋“œ๋ฐฑ์ด ๋“ค์–ด์™”๋‹ค.

๊ทธ๋ž˜์„œ ๋””๋ฐ”์šด์‹ฑ, ์„น์…˜ ๋ฆฌ์ŠคํŠธ, ์ €์žฅ๊นŒ์ง€ ํ•˜๋‚˜์”ฉ ๊ฐœ์„ ํ–ˆ๋‹ค.
ํŠนํžˆ useDebounce๋Š” ์–ด๋””๋“  ์“ธ ์ˆ˜ ์žˆ์–ด์„œ ์ง€๊ธˆ์€ ๊ณตํ†ต ํ›…์œผ๋กœ ๋นผ์„œ ์“ฐ๊ณ  ์žˆ๋‹ค.


๐Ÿ” ๊ฒ€์ƒ‰์€ ๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅ ๊ฐ™์ง€๋งŒ, ์ž‘์€ ๋””ํ…Œ์ผ์ด ์ „์ฒด UX๋ฅผ ๋ฐ”๊พผ๋‹ค.


profile
๊ฐœ๋ฐœ์ž๋กœ ์ทจ์—…์„ ์ค€๋น„ ์ค‘ ์ด๋ฉฐ, ์—ด์‹ฌํžˆ ๊ณต๋ถ€ ์ค‘ ์ž…๋‹ˆ๋‹ค!

0๊ฐœ์˜ ๋Œ“๊ธ€