๐ค ๋ค์ด๊ฐ๋ฉฐ
React Native ์ฑ์์ ๋ค๋น๊ฒ์ด์
์ ๊ตฌํํ ๋, ํ์ด์ง ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ ๋ฐฉ์์ ์ด๋ป๊ฒ ์ ํํ์๋์?
URL ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ ์ง, ์ ์ญ ์ํ ๊ด๋ฆฌ๋ฅผ ์ฌ์ฉํ ์ง๋ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ณ ๋ฏผํ๋ ๋ถ๋ถ์
๋๋ค.
์ต๊ทผ ํ๋ก์ ํธ์์ ์ฝํ
์ธ ์์ธ ํ์ด์ง์ ๋ผ์ฐํ
๋ฐฉ์์ URL ํ๋ผ๋ฏธํฐ์์ ์คํ ์ด ๊ธฐ๋ฐ์ผ๋ก ์ ๋ฉด ๋ฆฌํฉํ ๋งํ์ต๋๋ค.
์ด ๊ณผ์ ์์ ๋ฐฐ์ด ๊ฐ ๋ฐฉ์์ ์ฅ๋จ์ ๊ณผ ์ธ์ ์ด๋ค ๋ฐฉ์์ ์ ํํด์ผ ํ๋์ง์ ๋ํ ๊ฒฝํ์ ๊ณต์ ํ๊ณ ์ ํฉ๋๋ค.
๐ ๊ธฐ์กด ๋ฐฉ์: URL ํ๋ผ๋ฏธํฐ ๊ธฐ๋ฐ ๋ผ์ฐํ
๊ตฌํ ๋ฐฉ์
๊ธฐ์กด์๋ ์ ํ์ ์ธ URL ํ๋ผ๋ฏธํฐ ๋ฐฉ์์ ์ฌ์ฉํ์ต๋๋ค:
// ๊ธฐ์กด ํ์ผ ๊ตฌ์กฐ
src/app/content/detail/[id].tsx
// ๋ค๋น๊ฒ์ด์
const handleItemPress = (itemId: string) => {
router.push(`/content/detail/${itemId}`);
};
// ์์ธ ํ์ด์ง์์ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ
export default function ItemDetailScreen() {
const params = useLocalSearchParams<{ id?: string }>();
const itemId = params.id;
// API๋ก ์์ดํ
์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
const { data: item } = useGetItemById(itemId || '');
return (
<View>
{/* ์์ดํ
์์ธ ์ ๋ณด ๋ ๋๋ง */}
</View>
);
}
๐ฏ URL ํ๋ผ๋ฏธํฐ ๋ฐฉ์์ ์ฅ์
1. ์ง๊ด์ ์ธ URL ๊ตฌ์กฐ
/content/detail/item-123
/content/detail/item-456
URL๋ง ๋ด๋ ์ด๋ค ์์ดํ ์ ์์ธ ํ์ด์ง์ธ์ง ์ ์ ์์ต๋๋ค.
2. ๋ถ๋งํฌ ๋ฐ ๊ณต์ ๊ฐ๋ฅ
// URL์ ์ง์ ๊ณต์ ํ ์ ์์
const shareUrl = `myapp://content/detail/${itemId}`;
3. ๋ธ๋ผ์ฐ์ ์ ๋ค๋ก๊ฐ๊ธฐ ์ง์
์น ํ๊ฒฝ์์ ๋ธ๋ผ์ฐ์ ์ ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ์ด ์์ฐ์ค๋ฝ๊ฒ ๋์ํฉ๋๋ค.
4. SEO ์นํ์ (์น ์ฑ์ ๊ฒฝ์ฐ)
๊ฐ ํ์ด์ง๊ฐ ๊ณ ์ ํ URL์ ๊ฐ์ ธ ๊ฒ์ ์์ง ์ต์ ํ์ ์ ๋ฆฌํฉ๋๋ค.
๐จ ํ์ง๋ง ๋ฌธ์ ๊ฐ ์๊ฒผ์ต๋๋ค...
1. ๋ณต์กํ ๋ฐ์ดํฐ ์ ๋ฌ์ ํ๊ณ
์์ธ ํ์ด์ง์์๋ ๋จ์ํ ์์ดํ ID๋ง ํ์ํ ๊ฒ ์๋์์ต๋๋ค:
// ์ค์ ๋ก ํ์ํ๋ ๋ฐ์ดํฐ๋ค
interface DetailPageData {
selectedItemId: string; // ์ ํ๋ ์์ดํ
ID
allItems: Item[]; // ์ ์ฒด ์์ดํ
๋ชฉ๋ก (๋ค๋น๊ฒ์ด์
์ฉ)
category: string; // ์นดํ
๊ณ ๋ฆฌ ์ ๋ณด
searchContext: { // ๊ฒ์ ๋งฅ๋ฝ ์ ๋ณด
searchQuery: string;
filterOptions: FilterState;
};
}
์ด ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ URL ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ ค๋ฉด?
// ์ด๋ ๊ฒ ํด์ผ ํ ๋ปํ์ต๋๋ค... ๐ฑ
const queryString = `?id=${itemId}&category=${category}&filter=${JSON.stringify(filters)}&items=${JSON.stringify(allItems)}`;
router.push(`/content/detail${queryString}`);
2. ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฌธ์
// ๋ชฉ๋ก ํ์ด์ง์์ ์์ดํ
์ ํ ์
const handleItemPress = (item: Item) => {
// ์ด ๋ฐ์ดํฐ๋ค์ด ์์ธ ํ์ด์ง์ ์ด๋ป๊ฒ ์ ๋ฌ๋ ๊น์?
const allSearchResults = [item1, item2, item3, ...];
const currentCategory = "electronics";
router.push(`/content/detail/${item.id}`); // ID๋ง ์ ๋ฌ ๐ฐ
};
// ์์ธ ํ์ด์ง์์๋...
export default function ItemDetailScreen() {
const { id } = useLocalSearchParams();
// ๋ค์ API๋ฅผ ํธ์ถํด์ ๊ด๋ จ ์์ดํ
๋ค์ ๊ฐ์ ธ์์ผ ํจ
const { data: relatedItems } = useGetRelatedItems(id);
// ํ์ง๋ง ๊ฒ์ ๊ฒฐ๊ณผ์ ๋ค๋ฅผ ์ ์์! ๐
}
3. ์ฑ๋ฅ ์ด์
// ๋งค๋ฒ ์์ธ ํ์ด์ง์์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์๋ก ๋ก๋ฉ
const ItemDetailScreen = () => {
const { id } = useLocalSearchParams();
// ์ด๋ฏธ ์๋ ๋ฐ์ดํฐ์์๋ ๋ค์ API ํธ์ถ
const { data: item, isLoading } = useGetItemById(id);
const { data: reviews, isLoading: reviewsLoading } = useGetItemReviews(id);
const { data: related, isLoading: relatedLoading } = useGetRelatedItems(id);
if (isLoading || reviewsLoading || relatedLoading) {
return <LoadingSpinner />; // ๋ถํ์ํ ๋ก๋ฉ ์๊ฐ
}
return <DetailView />;
};
4. ๋ณต์กํ ์ํธ์์ฉ์ ํ๊ณ
// ๋ชฉ๋ก์์ ๋ค๋ฅธ ์์ดํ
์ผ๋ก ๋น ๋ฅธ ์ ํ ์
const handleQuickSwitch = (newItemId: string) => {
const selectedItem = allItems.find(item => item.id === newItemId);
// ์ด ์ ๋ณด๋ค์ ์ด๋ป๊ฒ ์ ๋ฌํ ๊น์?
// - selectedItem ๊ฐ์ฒด
// - allItems ๋ฐฐ์ด (๋ค๋น๊ฒ์ด์
์ฉ)
// - ํ์ฌ ํํฐ ์ํ
// - ๊ฒ์ ๋งฅ๋ฝ
router.push(`/content/detail/${newItemId}`); // ๊ฒฐ๊ตญ ID๋ง ์ ๋ฌ...
};
โจ ํด๊ฒฐ์ฑ : ์คํ ์ด ๊ธฐ๋ฐ ๋ผ์ฐํ ์ผ๋ก ์ ํ
์๋ก์ด ์ํคํ ์ฒ ์ค๊ณ
Zustand๋ฅผ ์ฌ์ฉํด์ ์์ธ ํ์ด์ง๋ฅผ ์ํ ์ ์ฉ ์คํ ์ด๋ฅผ ๋ง๋ค์์ต๋๋ค:
// src/store/detailStore.ts
import { create } from 'zustand';
import { Item } from '@/api/types/item';
interface DetailState {
selectedItemId: string | null;
allItems: Item[];
category: string;
filterState: FilterState;
setDetailData: (
itemId: string,
allItems?: Item[],
category?: string,
filterState?: FilterState
) => void;
clearDetailData: () => void;
}
export const useDetailStore = create<DetailState>((set) => ({
selectedItemId: null,
allItems: [],
category: '',
filterState: {},
setDetailData: (itemId, allItems = [], category = '', filterState = {}) => {
set({
selectedItemId: itemId,
allItems,
category,
filterState,
});
},
clearDetailData: () => {
set({
selectedItemId: null,
allItems: [],
category: '',
filterState: {},
});
},
}));
ํ์ผ ๊ตฌ์กฐ ๋จ์ํ
// Before: ๋์ ๋ผ์ฐํธ
src/app/content/detail/[id].tsx
// After: ์ ์ ๋ผ์ฐํธ
src/app/content/detail.tsx
URL ํ๋ผ๋ฏธํฐ๊ฐ ํ์ ์์ด์ก์ผ๋ ํ์ผ ๊ตฌ์กฐ๋ ๋ ๊ฐ๋จํด์ก์ต๋๋ค.
์๋ก์ด ๋ค๋น๊ฒ์ด์ ํ๋ก์ฐ
// ๋ชฉ๋ก ํ์ด์ง์์ ์์ดํ
์ ํ
const handleItemPress = (item: Item) => {
console.log('์์ดํ
์ ํ:', item);
// 1. ์คํ ์ด์ ๋ชจ๋ ํ์ํ ๋ฐ์ดํฐ ์ ์ฅ
setDetailData(item.id, searchResults, category, currentFilters);
// 2. ๊ฐ๋จํ ๋ผ์ฐํ
router.push('/content/detail');
};
// ๋น ๋ฅธ ์์ดํ
์ ํ
const handleQuickSwitch = (newItemId: string) => {
const item = allItems.find((item) => item.id === newItemId);
if (item) {
// ์ ํ๋ ์์ดํ
ID๋ง ์
๋ฐ์ดํธ (๋ค๋ฅธ ๋ฐ์ดํฐ๋ ์ ์ง)
setSelectedItemId(item.id);
}
};
์์ธ ํ์ด์ง ๊ตฌํ
// src/app/content/detail.tsx
export default function ItemDetailScreen() {
const {
selectedItemId,
allItems,
category,
filterState
} = useDetailStore();
// ์คํ ์ด์ ID๋ก API ํธ์ถ (์บ์ฑ ํ์ฉ ๊ฐ๋ฅ)
const { data: selectedItem } = useGetItemById(selectedItemId || '');
// ๋ค๋น๊ฒ์ด์
์ ๋ณด๋ ์คํ ์ด์์ ๋ฐ๋ก ์ฌ์ฉ
const navigationItems = useMemo(() =>
allItems.map((item) => ({
id: item.id,
title: item.title,
isSelected: selectedItem?.id === item.id,
})),
[allItems, selectedItem?.id]
);
return (
<View>
{/* ์์ดํ
๋ค๋น๊ฒ์ด์
*/}
<ItemNavigation
items={navigationItems}
onItemSelect={handleQuickSwitch}
/>
{/* ์ ํ๋ ์์ดํ
์ ์์ธ ์ ๋ณด */}
<ScrollView>
<ItemInfo item={selectedItem} />
<RelatedSection category={category} />
</ScrollView>
</View>
);
}
๐ ๋น๊ต: Before vs After
๋ฐ์ดํฐ ํ๋ก์ฐ ๋น๊ต
Before (URL ํ๋ผ๋ฏธํฐ):
๋ชฉ๋ก ํ์ด์ง → URL ํ๋ผ๋ฏธํฐ → ์์ธ ํ์ด์ง → API ์ฌํธ์ถ → ๋ ๋๋ง
↓ ↓ ↓ ↓
[๋ณต์กํ ๋ฐ์ดํฐ] [ID๋ง ์ ๋ฌ] [๋งฅ๋ฝ ์์ค] [์ค๋ณต ๋ก๋ฉ]
After (์คํ ์ด ๊ธฐ๋ฐ):
๋ชฉ๋ก ํ์ด์ง → ์คํ ์ด ์ ์ฅ → ์์ธ ํ์ด์ง → ์คํ ์ด์์ ์กฐํ → ๋ ๋๋ง
↓ ↓ ↓ ↓
[๋ชจ๋ ๋ฐ์ดํฐ] [์ ์ฒด ์ ์ฅ] [๋งฅ๋ฝ ์ ์ง] [์ฆ์ ํ์]
์ฑ๋ฅ ๋น๊ต
ํญ๋ชฉ URL ํ๋ผ๋ฏธํฐ ์คํ ์ด ๊ธฐ๋ฐ
| ์ด๊ธฐ ๋ก๋ฉ ์๊ฐ | 3-4๊ฐ API ํธ์ถ | ์บ์๋ ๋ฐ์ดํฐ ํ์ฉ |
| ์์ดํ ์ ํ ๋ฐ์ | ํ์ด์ง ๋ฆฌ๋ก๋ | ์ฆ์ ์ ๋ฐ์ดํธ |
| ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ | API ์๋ต ์ค๋ณต ์ ์ฅ | ์คํ ์ด์์ ๊ณต์ |
| ๋คํธ์ํฌ ์์ฒญ | ๋งค๋ฒ ์ ์์ฒญ | ํ์์์๋ง ์์ฒญ |
โ๏ธ ํธ๋ ์ด๋์คํ: ๋ฌด์์ ์๊ณ ๋ฌด์์ ์ป์๋?
๐ ์์ ๊ฒ๋ค
1. URL ๊ธฐ๋ฐ ๋ค๋น๊ฒ์ด์
// ๋ ์ด์ ๋ถ๊ฐ๋ฅ
const directUrl = `/content/detail/${itemId}`;
2. ๋ฅ๋งํฌ ์ง์์ ๋ณต์ก์ฑ
// ๋ฅ๋งํฌ ์ฒ๋ฆฌ ์ ์คํ ์ด ์ด๊ธฐํ ํ์
const handleDeepLink = (url: string) => {
const itemId = extractItemId(url);
// ์คํ ์ด์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด API๋ก ๊ฐ์ ธ์์ผ ํจ
await initializeStoreForItem(itemId);
};
3. ๋ธ๋ผ์ฐ์ ํ์คํ ๋ฆฌ ๊ด๋ฆฌ
์น ํ๊ฒฝ์์๋ ์ถ๊ฐ์ ์ธ ํ์คํ ๋ฆฌ ๊ด๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.
๐ ์ป์ ๊ฒ๋ค
1. ๋น ๋ฅธ ๋ค๋น๊ฒ์ด์
// ์ฆ์ ํ์ด์ง ์ ํ, ๋ก๋ฉ ์์
setDetailData(item.id, allItems, category);
router.push('/content/detail'); // 0.1์ด ๋ด ํ๋ฉด ์ ํ
2. ๋ฐ์ดํฐ ์ผ๊ด์ฑ
// ๊ฒ์ ๊ฒฐ๊ณผ์ ์์ธ ํ์ด์ง์ ๋ฐ์ดํฐ๊ฐ ํญ์ ๋์ผ
const { allItems } = useDetailStore(); // ๊ฒ์ ๊ฒฐ๊ณผ ๊ทธ๋๋ก
3. ๋ณต์กํ ์ํธ์์ฉ ์ง์
// ๋น ๋ฅธ ์์ดํ
์ ํ
const handleQuickSwitch = (itemId: string) => {
setSelectedItemId(itemId); // ์ฆ์ ๋ฐ์
};
4. ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ
// ํ ๋ฒ ๋ก๋๋ ๋ฐ์ดํฐ๋ ์คํ ์ด์์ ์ฌ์ฌ์ฉ
const cachedData = useDetailStore(); // API ์ฌํธ์ถ ๋ถํ์
๐ฏ ์ธ์ ์ด๋ค ๋ฐฉ์์ ์ ํํด์ผ ํ ๊น?
URL ํ๋ผ๋ฏธํฐ๊ฐ ์ ํฉํ ๊ฒฝ์ฐ
1. ๋จ์ํ ๋ฐ์ดํฐ ์ ๋ฌ
// ์ฌ์ฉ์ ํ๋กํ ํ์ด์ง
router.push(`/user/${userId}`); // ID๋ง ์์ผ๋ฉด ์ถฉ๋ถ
2. SEO๊ฐ ์ค์ํ ์น ์ฑ
// ๋ธ๋ก๊ทธ ํฌ์คํธ
router.push(`/posts/${postSlug}`); // ๊ฒ์ ์์ง ์ต์ ํ ํ์
3. ๋ฅ๋งํฌ๊ฐ ํต์ฌ ๊ธฐ๋ฅ
// ์ํ ์์ธ ํ์ด์ง
const shareUrl = `myapp://product/${productId}`; // ๊ณต์ ๊ธฐ๋ฅ ์ค์
์คํ ์ด ๊ธฐ๋ฐ์ด ์ ํฉํ ๊ฒฝ์ฐ
1. ๋ณต์กํ ๋ฐ์ดํฐ ๊ตฌ์กฐ
// ๋ค๋จ๊ณ ํผ, ์ผํ ์นดํธ, ๋ณต์กํ ์ค์
interface ComplexData {
step1: FormData;
step2: FormData;
context: AppContext;
}
2. ๋น ๋ฅธ ์ฌ์ฉ์ ๊ฒฝํ์ด ์ค์
// ๋์๋ณด๋, ์ค์๊ฐ ๋ฐ์ดํฐ
setData(complexData);
router.push('/dashboard'); // ์ฆ์ ์ ํ
3. ์ํ ๊ณต์ ๊ฐ ํ์
// ์ฌ๋ฌ ํ์ด์ง์์ ๊ฐ์ ๋ฐ์ดํฐ ์ฌ์ฉ
const sharedState = useGlobalStore();
๐ ๏ธ ์ค์ ๊ตฌํ ํ
1. ์คํ ์ด ์ด๊ธฐํ ์ฒ๋ฆฌ
export default function DetailScreen() {
const { selectedItemId } = useDetailStore();
// ์คํ ์ด๊ฐ ๋น์ด์์ผ๋ฉด ํ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
useEffect(() => {
if (!selectedItemId) {
router.replace('/(tabs)/home');
}
}, [selectedItemId]);
}
2. ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ
export default function DetailScreen() {
const { clearDetailData } = useDetailStore();
// ์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ์ ๋ฆฌ
useEffect(() => {
return () => {
clearDetailData();
};
}, []);
}
3. ํ์ ์์ ์ฑ ํ๋ณด
interface DetailState {
selectedItemId: string | null; // null ์ฒดํฌ ํ์
}
const { selectedItemId } = useDetailStore();
if (!selectedItemId) {
return <NotFoundScreen />;
}
๐ฏ ๋ง๋ฌด๋ฆฌ
URL ํ๋ผ๋ฏธํฐ์ ์คํ ์ด ๊ธฐ๋ฐ ๋ผ์ฐํ ๊ฐ๊ฐ์ ์ฅ๋จ์ ์ ์ดํดํ๊ณ , ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ์ ๋ง๋ ์ ํ์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๐ ํต์ฌ ํ๋จ ๊ธฐ์ค
- โ ๋จ์ํ ID ์ ๋ฌ: URL ํ๋ผ๋ฏธํฐ
- โ ๋ณต์กํ ๋ฐ์ดํฐ ์ ๋ฌ: ์คํ ์ด ๊ธฐ๋ฐ
- โ SEO/๋ฅ๋งํฌ ์ค์: URL ํ๋ผ๋ฏธํฐ
- โ ๋น ๋ฅธ UX ์ค์: ์คํ ์ด ๊ธฐ๋ฐ
๊ฐ๋ฐํ ์ฑ์ ํน์ฑ์ ๋ณต์กํ ๋ฐ์ดํฐ ์ ๋ฌ๊ณผ ๋น ๋ฅธ ์ฌ์ฉ์ ๊ฒฝํ์ด ๋ ์ค์ํ๊ธฐ ๋๋ฌธ์ ์คํ ์ด ๊ธฐ๋ฐ์ผ๋ก ์ ํํ ๊ฒ์ด ์ณ์ ์ ํ์ด์์ต๋๋ค.
'Frontend > React' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [React] React์์ AI ํ์ดํ ํจ๊ณผ ๊ตฌํํ๊ธฐ | setInterval์ ํจ์ ๊ณผ ํด๊ฒฐ๋ฒ (0) | 2025.09.16 |
|---|---|
| [React] React Hook "useEffect" is called in function ์๋ฌ ํด๊ฒฐ (0) | 2025.03.03 |
| [React] .env ํ๊ฒฝ๋ณ์ ์ฌ์ฉ๋ฒ (Vite / CRA ํ๊ฒฝ) (0) | 2025.02.27 |
| [React] Props, Event, State (2) | 2024.12.19 |
| [React] ์ปดํฌ๋ํธ์ ๋ํ ๊ณ ์ฐฐ (3) | 2024.12.18 |