프론트엔드 애플리케이션의 상태 배치 탐색
Grace Collins
Solutions Engineer · Leapcell

프론트엔드 상태는 어디에 속해야 할까요?
프론트엔드 개발의 핵심은 상태 관리입니다. 사용자 입력, API에서 가져온 데이터 또는 현재 보기에 대한 구성이든 상태는 애플리케이션의 동작 방식과 사용자가 보는 것을 결정합니다. 그러나 이 상태를 어디에 저장할지 결정하는 것은 영원한 과제입니다. 이 질문에 올바르게 답하는 것은 유지 관리 가능하고 확장 가능하며 성능 좋은 애플리케이션을 구축하는 데 중요합니다. 잘못 배치된 상태는 prop drilling, 어려운 디버깅 및 전반적으로 파편화된 사용자 경험으로 이어질 수 있습니다. 이 글에서는 로컬, 전역 및 URL 상태 간의 미묘한 상호 작용을 탐구하고, 개발자가 정보에 입각한 결정을 내릴 수 있도록 역할, 이점 및 실용적인 응용 프로그램을 분석합니다.
핵심 상태 개념 이해
상태 배치에 대한 구체적인 내용을 살펴보기 전에 논의할 기본 상태 유형을 정의해 보겠습니다.
- 로컬 상태: 단일 구성 요소 내에 완전히 포함되며 (명시적으로 props나 콜백을 통해 전달되지 않는 한) 형제 또는 부모 구성 요소에 직접 액세스하거나 관련이 없는 상태를 의미합니다. 공유할 필요가 없는 UI 관련 문제를 해결하는 데 자주 사용됩니다.
- 전역 상태: 애플리케이션의 여러, 종종 서로 다른 구성 요소에서 액세스하고 잠재적으로 수정해야 하는 상태를 의미합니다. 사용자 인터페이스의 다양한 부분에 영향을 미치는 공유 데이터를 나타냅니다.
- URL 상태: 이 유형의 상태는 브라우저 URL에 직접 포함됩니다. 주로 애플리케이션의 현재 "보기" 또는 "페이지"를 나타내는 데 사용되며, 딥 링크, 브라우저 기록 탐색 및 특정 애플리케이션 상태를 공유하는 기능을 활성화합니다.
로컬 상태: 구성 요소의 개인 도메인
로컬 상태는 가장 간단한 상태 관리 형태이며 가능한 경우 기본 선택 사항이어야 합니다. 구성 요소의 관심사를 캡슐화하여 이해, 테스트 및 재사용을 더 쉽게 만듭니다.
근거: 상태의 일부가 단일 구성 요소와 그 즉각적인 하위 구성 요소 (props를 통해)에만 영향을 미치는 경우 이를 승격시킬 필요가 없습니다. 로컬 상태를 유지하면 버그 발생 가능성이 줄어들고 구성 요소 격리가 개선됩니다.
구현 예시 (React):
import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); // 'count'는 로컬 상태입니다. const increment = () => { setCount(prevCount => prevCount + 1); }; return ( <div> <p>Count: {count}</p> <button onClick={increment}>Increment</button> </div> ); } export default Counter;
응용 시나리오:
- 양식 입력 값: 사용자가 입력 필드에 입력하는 내용.
- 토글 상태: 드롭다운 메뉴의 열림/닫힘 상태.
- 로딩 표시기: 단일 구성 요소의 데이터 가져오기를 위한 부울 플래그
isLoading. - 구성 요소별 UI 애니메이션: 요소의 애니메이션 단계를 제어하는 상태 변수.
전역 상태: 공유되는 진실의 원천
여러 구성 요소 (구성 요소 트리에서 멀리 떨어진 종종)가 동일한 데이터에 액세스하거나 수정해야 할 때 전역 상태가 필수적입니다. 실제로 필요하지 않은 구성 요소를 통해 여러 계층을 통과하는 props 전달인 "prop drilling"을 방지합니다.
근거: 공유 상태를 중앙 집중화하면 단일 진실의 원천이 가능해져 업데이트가 예측 가능하고 모든 관련 구성 요소가 일관되게 반응하도록 보장합니다.
구현 예시 (React with Context API):
// UserContext.js import React, { createContext, useState, useContext } from 'react'; const UserContext = createContext(null); export const UserProvider = ({ children }) => { const [user, setUser] = useState({ name: 'Guest', isAuthenticated: false }); const login = (username) => setUser({ name: username, isAuthenticated: true }); const logout = () => setUser({ name: 'Guest', isAuthenticated: false }); return ( <UserContext.Provider value={{ user, login, logout }}> {children} </UserContext.Context> ); }; export const useUser = () => useContext(UserContext); // Header.js import React from 'react'; import { useUser } from './UserContext'; function Header() { const { user, logout } = useUser(); return ( <header> <span>Hello, {user.name}</span> {user.isAuthenticated && <button onClick={logout}>Logout</button>} </header> ); } // App.js import React from 'react'; import { UserProvider } from './UserContext'; import Header from './Header'; import UserProfile from './UserProfile'; // 이 구성 요소도 useUser()를 사용한다고 가정 function App() { return ( <UserProvider> <Header /> {/* 사용자 정보를 필요로 할 수 있는 다른 구성 요소 */} <UserProfile /> </UserProvider> ); } export default App;
응용 시나리오:
- 인증 상태: 사용자가 로그인했는지 여부 및 프로필 정보.
- 테마/현지화: 현재 테마 (어둡게/밝게), 선택한 언어.
- 쇼핑 카트 항목: 다른 제품 페이지에 걸쳐 카트에 추가된 제품 목록.
- 전역 알림: 애플리케이션 전체에 표시되는 토스트 메시지 또는 경고.
- 복잡한 양식 마법사: 여러 단계의 다단계 양식에 걸쳐 공유해야 하는 상태.
URL 상태: 영구 보기 식별자
URL 상태는 브라우저에서 관리하며 애플리케이션의 현재 보기를 공유 가능하고 북마크 가능한 방식으로 나타내는 방법을 제공한다는 점에서 고유합니다.
근거: 딥 링크, 브라우저 기록 탐색 또는 강력한 새로 고침 기능을 필요로 하는 애플리케이션의 경우 URL에 상태를 인코딩하는 것은 필수적입니다. 사용자가 애플리케이션의 특정 보기를 공유할 수 있도록 하고 페이지를 새로 고침할 때 동일한 상태로 돌아가도록 보장합니다.
구현 예시 (React with URLSearchParams and useLocation/useNavigate from react-router-dom):
import React, { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; function ProductList() { const location = useLocation(); const navigate = useNavigate(); // URL에서 필터 추출 const searchParams = new URLSearchParams(location.search); const initialCategory = searchParams.get('category') || 'all'; const [selectedCategory, setSelectedCategory] = useState(initialCategory); const [products, setProducts] = useState([]); // 이는 일반적으로 API 가져오기에서 제공됩니다. // 카테고리 기반으로 제품 가져오기 시뮬레이션 useEffect(() => { console.log(`Fetching products for category: ${selectedCategory}`); // 실제 앱에서는 여기서 데이터를 가져옵니다. const fetchedProducts = selectedCategory === 'electronics' ? [{ id: 1, name: 'Laptop' }, { id: 2, name: 'Mouse' }] : [{ id: 3, name: 'T-Shirt' }, { id: 4, name: 'Jeans' }]; setProducts(fetchedProducts); }, [selectedCategory]); const handleCategoryChange = (event) => { const newCategory = event.target.value; setSelectedCategory(newCategory); // 새 카테고리를 반영하도록 URL 업데이트 const newSearchParams = new URLSearchParams(); if (newCategory !== 'all') { newSearchParams.set('category', newCategory); } navigate(`?${newSearchParams.toString()}`, { replace: true }); }; return ( <div> <h1>Products</h1> <label htmlFor="category-select">Filter by Category:</label> <select id="category-select" value={selectedCategory} onChange={handleCategoryChange}> <option value="all">All</option> <option value="electronics">Electronics</option> <option value="clothing">Clothing</option> </select> <ul> {products.map(product => ( <li key={product.id}>{product.name}</li> ))} </ul> </div> ); } // 메인 App 구성 요소 또는 라우터 설정에서: // <Router> // <Routes> // <Route path="/products" element={<ProductList />} /> // </Routes> // </Router>
응용 시나리오:
- 검색 쿼리 매개변수:
?query=react&page=2 - 필터링 및 정렬 옵션:
?category=electronics&sort=price_asc - 탭 선택:
?tab=profile - 모달 표시 (가능성은 낮지만 가능):
?modal=login - 특정 항목 ID:
/products/123(경로 매개변수도 URL 상태의 한 형태입니다.)
결론
상태를 어디에 배치할지 결정하는 것은 프론트엔드 아키텍처의 기본 측면입니다. 로컬 상태는 캡슐화와 단순성을 촉진하여 구성 요소별 관심사에 대한 기본 선택 사항입니다. 전역 상태는 애플리케이션 전체 데이터에 대한 공유된 진실의 원천을 제공하고, prop drilling을 제거하며, 상호 연결된 구성 요소의 일관성을 개선합니다. URL 상태는 공유 가능하고 영구적인 보기, 브라우저 기록 통합을 제공하여 애플리케이션의 보기를 나타내는 데 필수적입니다. 이러한 원칙에 따라 상태를 신중하게 분류하고 배치함으로써 개발자는 강력하고 유지 관리 가능하며 사용자 친화적인 프론트엔드 애플리케이션을 구축할 수 있습니다. 먼저 로컬 상태를 선택하고, 필요한 경우 전역 상태를, 공유 가능하고 영구적인 보기를 위해 URL 상태를 선택하십시오.

