React가 UI를 컴포넌트로 나누고 상태에 따라 선언적으로 렌더링하는 방식을 기초부터 설명한다. JSX와 props에서 시작해 비동기 상태 갱신, 불변성, 재조정, 클래스 컴포넌트와 Hook의 차이까지 코드 예제로 연결한다. React API를 외우기보다 왜 이런 구조가 필요한지 이해하는 데 초점을 둔다.
핵심 포인트- React는 복잡한 DOM 조작을 직접 명령하는 대신, 현재 상태에서 어떤 UI가 보여야 하는지를 선언하게 한다.
- JSX는 마크업과 로직을 컴포넌트라는 단위에 함께 두며 Babel을 거쳐 React element 생성 코드로 변환된다.
- state나 props가 바뀌면 새 element 트리가 만들어지고, 재조정 과정이 실제 DOM에서 달라진 최소 부분만 갱신한다.
- 얕은 비교 기반 최적화는 불변 데이터와 함께 써야 하며, 배열이나 객체를 직접 변경하면 화면 갱신이 누락될 수 있다.
- Hook은 클래스 없이 상태를 다루고, 별도 래퍼 없이 상태 관련 로직을 재사용하게 한다.
상세 정리- 출발점: 초기 웹은 노드와 이벤트가 적어 DOM API를 직접 호출해도 관리할 수 있었지만, 페이지가 복잡해지며 명령형 코드의 흐름을 추적하기 어려워졌다.
- 컴포넌트: React는 마크업과 동작을 무조건 분리하지 않는다. 서로 관련된 UI와 로직을 하나의 컴포넌트로 묶고 큰 화면을 작은 관심사로 나눈다.
- JSX 변환: JSX는 JavaScript도 HTML도 아닌 확장 문법이다. Babel이 React.createElement 호출로 변환하고, 그 결과인 element 객체를 ReactDOM이 실제 DOM에 반영한다.
- props 전달: 사용자 정의 컴포넌트의 JSX attribute와 children은 props 객체로 전달된다. 컴포넌트는 이 입력을 받아 element를 반환한다.
- 선언형 렌더링: DOM API로 변경 순서를 일일이 기술하는 대신 상태와 그 상태에서 보일 결과를 선언한다. React가 갱신 시점과 적용할 노드를 결정한다.
- 상태 스케줄링: 같은 이벤트 안에서 setState를 연속 호출해도 매번 즉시 렌더링하지 않는다. React가 호출을 스케줄링하고 묶어 렌더 횟수를 줄인다.
- 자식 렌더링: 부모가 다시 렌더링되면 자식도 호출될 수 있다. shouldComponentUpdate나 PureComponent는 props와 state의 얕은 비교로 불필요한 렌더링을 건너뛴다.
- 불변성 함정: 배열에 push한 뒤 같은 참조를 다시 저장하면 얕은 비교에서 변화가 없다고 판단할 수 있다. 전개 연산자로 새 배열을 만들거나 Immer로 새 상태를 생성해야 한다.
- 재조정: render가 다시 호출됐다고 실제 DOM 노드까지 모두 바뀌지는 않는다. React가 이전·새 element 트리를 비교해 변경된 부분만 반영한다.
- Hook 전환: useState는 함수 컴포넌트에 상태를 제공하고, custom Hook은 HOC나 render props의 래퍼 계층 없이 로직을 공유한다.
- Hook 주의점: useState의 setter는 클래스 setState처럼 객체를 자동 병합하지 않는다. 필요한 필드를 직접 합치거나 상태를 나누고, 복잡한 전이는 useReducer로 표현할 수 있다.
- 함수 컴포넌트 최적화: useMemo는 계산 결과를 의존성에 따라 재사용하고 React.memo는 PureComponent처럼 props 얕은 비교를 적용한다.
왜 읽나React를 처음 접하거나 API 사용법은 알지만 컴포넌트·상태·렌더링 최적화가 연결되는 원리를 다시 잡고 싶은 프론트엔드 개발자에게 유용하다.