Jin@jin

Dev Hub

2026년 React를 사용하면서 중요하게 느낀 설계 원칙

리액트를 계속 사용해보면서 느끼는건, 좋은 리액트 앱은 컴포넌트를 많이 만드는 것이 아니라 "상태"를 어디에 둘지 결정하는 것에 가깝다는 점입니다.

초기 리액트로 개발할때는 컴포넌트를 잘게 나누고, 공통 컴포넌트 잘 만들고, 전역 상태 구조 잘 설계하는 것을 리액트 개발의 핵심처럼 생각했었습니다.

하지만 실제 리액트 앱을 운영하다 보면 더 중요한 질문은 따로 있습니다.

이 상태는 정말 클라이언트에 있어야 하나?
이 상태는 새로고침 후에도 유지되야 하나?

이 상태는 URL로 나타낼 수 없나?

이 상태는 그냥 서버 데이터 복사본 아닌가?

상태는 적을수록 좋다

예전에는 Redux, Recoil, Zustand 등 전역 상태 관리 도구를 많이 사용했습니다.

특히 초기 CSR이 주류이던 초기 시절에는 전역 상태 설계를 컴포넌트 개발보다 중요하게 고민하기도 했습니다.

지금은 우선순위가 달라졌습니다.

  1. 서버 상태: DB, API, loader, query cache
  2. URL 상태: 쿼리스트링, 경로 파라미터
  3. 로컬 상태: useState
  4. 전역 상태: Zustand, useContext 등

전역 상태는 마지막 선택지에 가깝습니다.

예를 들어 정렬 방식, 검색어, 페이지 번호, 탭 상태는 로컬 상태보다 URL에 두는 편이 더 좋은 경우가 많습니다.

bash

이렇게 두면 새로고침, 공유, 뒤로가기, 브라우저 동작과 자연스럽게 연결됩니다.

반대로 서버에서 가져온 데이터를 다시 useState에 복사해서 관리하기 시작하면 캐시, 동기화, refetch 등을 직접 관리해야 합니다.

상태를 잘 관리하는 가장 좋은 방법은 상태를 많이 만드는 것이 아니라, 불필요한 상태를 만들지 않는 것입니다.

useEffect는 최소화한다

예전에는 컴포넌트가 마운트되면 useEffect 안에서 데이터를 가져오는 패턴을 많이 썼었죠. (특히 초기 CSR 시절)

tsx

물론 틀린 코드는 아니지만, 이 방식은 규모가 커지면 금방 복잡해집니다.

로딩 상태, 에러 상태, 중복 요청, 요청 취소, 캐시, refetch 등을 직접 다뤄야 하기 때문입니다.

요즘은 데이터 로딩이라면 React Query나 React Router의 loader를 먼저 고려합니다.

typescript

useEffect는 데이터 로딩 도구가 아니라 리액트 상태 동기화 도구에 가깝습니다.

외부 이벤트 구독, IntersectionObserver 연결, WebSocket 연결 같은 경우에는 여전히 적절하지만 API 데이터를 가져오기 위한 사용은 가능하면 줄이는 편이 좋습니다.

컴포넌트보다 사용자 흐름을 설계한다

버튼 컴포넌트 하나를 잘 만드는 것도 중요하지만, 실제 서비스에서는 버튼 자체보다 버튼을 눌렀을 때의 흐름이 더 중요합니다.

예를 들어 작성 버튼 하나에도 여러 상태가 숨어 있습니다.

  • 요청 중인지
  • 실패했을 때 어떻게 보여줄지
  • 입력값이 유효하지 않을 때 어떻게 막을지
  • 권한이 없을 때 어떤 안내를 보여줄지
  • 중복 클릭을 막을지
  • 성공 후 어디로 이동할지

실제 서비스 버그 대부분은 UI보다 흐름에서 발생합니다.

컴포넌트 코드가 보기 좋고 예쁘게 렌더링되지만, 로딩 중에 두 번 제출되거나, 권한 없는 사용자가 버튼을 누를 수 있거나, 에러가 발생했는데 아무 피드백이 없다면 좋은 UI라고 보기 어렵습니다.

그래서 컴포넌트 설계할 때는 "어떤 상태들을 지나갈까?"를 중요하게 고민하는게 좋습니다.

재사용 가능한 컴포넌트보다 조합 가능한 컴포넌트

처음에는 재사용 가능한 컴포넌트를 잘 만드는 것이 컴포넌트를 잘 만드는 것처럼 보입니다.

하지만 재사용을 너무 의식하면 오히려 컴포넌트가 복잡해집니다.

공통 컴포넌트 하나가 10~20개 props를 받기 시작하면 이건 재사용이 아니라 기술 부채에 가까워지죠.

tsx

이런 컴포넌트는 처음에는 편해보이지만, 시간이 지날수록 수정하기 어려워집니다.

요즘은 "모든 상황을 처리하는 컴포넌트"보다 "작은 조각으로 조합할 수 있는 컴포넌트"가 더 유지보수하기 좋다고 평가 받습니다.

Radix UI와 Base UI가 대표적인 예시입니다.

tsx

재사용성보다 중요한 것은 변경 가능성입니다.

성능 최적화는 만능이 아니다

React.memo, useMemo, useCallback 는 마법처럼 성능을 개선해주는 도구가 아닙니다.

이 도구들도 사용하는 순간 비용이 생깁니다.

사용하면 코드가 복잡해지는 것, 의존성 배열을 관리해야 하는 것과 같은 문제가 생깁니다.

잘못 사용하면 오히려 의미 없는 최적화가 되기 쉽습니다.

먼저 구조를 단순하게 만들고, 실제 병목이 측정된 곳에만 최적화를 적용해주는 편이 좋습니다.

좋은 리액트 코드는 작동하는 코드가 아니다

좋은 리액트 코드는 단순히 화면이 정상적으로 나오는 코드가 아닙니다.

좋은 코드는 새 팀원이 봐도 이해할 수 있고, 6개월 뒤에도 수정 가능하며, 기능 추가가 쉬운 코드입니다.

리액트를 오래할수록 "어떻게 만들까?" 보다 "어떻게 덜 복잡하게 만들까?"를 더 고민하게 됩니다.

결국 리액트 설계의 핵심은 구조의 복잡도를 어디까지 낮출 수 있는지에 있는 것 같습니다.

AI 시대에 설계는 더 중요하다

특히나 요즘은 Cursor, Claude Code 등 AI 개발 도구 덕분에 컴포넌트 구현 자체는 훨씬 쉬워졌습니다. 단순 CRUD 구현과 UI 구현은 이제 대부분 AI가 코드 작성해줍니다.

하지만 이미 만들어진 구조와 설계가 복잡하다면, AI도 그 구조와 설계를 유지한채 근본적인 해결이 안된 상황에서 코드 구현이 계속 이어질 수 있습니다.

앞으로 이제 좋은 리액트 개발자는 컴포넌트 구현 하나가 빠른 개발자 보다, AI가 생성한 코드를 장기적으로 유지 가능한 구조인지 판단할 수 있는 사람이 되지 않을까 합니다.

Loading comments…