
애플리케이션 내에서 어떤 데이터나 상태가 오직 한 곳에서만 관리되고, 그 한 곳을 통해서만 읽고 쓰기를 하는 설계 원칙
장점
1. 일관성 유지: 중복된 데이터가 여러 군데에 흩어져 있지 않기 때문에, 값이 변경될 때마다 모든 복제본을 동기화할 필요가 없다.
2. 버그 감소: 변경 지점을 한 군데로 제한할 수 있으므로, 어디서 데이터가 바뀌는지 추적하기 쉽고 예측 가능성이 높아진다.
3. 유지보수 편리: 데이터 흐름을 한 눈에 파악할 수 있어 리팩토링이나 기능 추가 시 안정적으로 작업할 수 있다.
// ❌ 나쁜 예: 부모와 자식이 각각 로컬 state를 갖고 있을 때
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onChange={setCount} />;
}
function Child({ count, onChange }) {
const [localCount, setLocalCount] = useState(count);
// 부모가 바꿔도 자식 로컬 state는 동기화가 안 될 수 있음
}
// ✅ 좋은 예: 단일 state를 부모에서만 관리
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onChange={setCount} />;
}
function Child({ count, onChange }) {
// 부모가 관리하는 count만 사용
return <button onClick={() => onChange(count + 1)}>{count}</button>;
}
그러나 데이터 일관성이 중요한 대부분의 상황에서는
단일 진실의 원천을 지키는 것이 유지보수성과 신뢰성을 크게 높여 준다.
const ProductListPage = () => {
const { products, productsError, isProductsLoading } = useProductsContext();
const { shoppingCartError } = useShoppingCartContext();
return (
<>
{productsError.isError && (
<ErrorToast errorMessage={productsError.errorMessage} />
)}
{shoppingCartError.isError && (
<ErrorToast errorMessage={shoppingCartError.errorMessage} />
)}
<ProductListToolBar />
{isProductsLoading ? (
<OrbitSpinner />
) : (
<ProductCardList products={products} />
)}
</>
);
};
const ShoppingCartProvider: React.FC<PropsWithChildren> = ({ children }) => {
const { data, error, isLoading } = useGetShoppingCart();
const [cartItems, setCartItems] = useState<CartItem[]>([]);
const [shoppingCartError, setShoppingCartError] = useState<Error>(INITIAL_ERROR);
const [isShoppingLoading, setIsShoppingLoading] = useState<boolean>(false);