[React] 공식문서 톺아보기 1

Part 0. 이야기에 앞서

나는 몰랐어요. 내가 리액트를 만지게 될지.


언젠가 프론트엔드도 다뤄보고싶다, 공부해보고 싶다 막연히 생각만 했지, 이렇게 어느날 갑자기 정신차려보니 “풀스택 개발자”로 데뷔하게 될 줄은 몰랐다.

예전에야 이것도 저것도 다 잘하는 만능 슈퍼 개발자가 되어야지, 했지만 역시나 공부의 길은 끝이 없고 백엔드만, 거기서도 자프링 위주로 해왔지만 아직도 갈 길은 멀다. 그래서 올해 k8s 공부도 큰 맘 먹고 시작해서 열심히 쿠브씨티엘 중인데, 회사에서 신규 프로젝트를 하는데 풀스택 느낌의 포지션이라 리액트를 하게됐다.

요새 나는 케르베로스 같다. 자프링 머리, k8s 머리, 리액트 머리가 달린.. 안 쓰는 머리가 떨어져나가지 않도록 라운드로빈으로 짱구를 굴려야 하는.

무튼! 회사서는 윈도우, 집에서는 맥을 쓰는 것으로 멀티태스킹을 연습해왔으니, 머리 세개도 견지해보자 (?)

여기까지는 내가 리액트를 하게 된 배경.


다행인점은 내 맞은편에 리액트 초고수가 계시다는 점이다.

물론, 내가 백엔드 전반, 그 분이 프론트엔드 전반을 다뤄도 되긴한다. 그런데 이 분이랑 나는 같이 공부하면 n배로 공부 가능하다. 는 모토와 기타등등의 이유로 각자의 영역을 메인으로 반대도 다뤄보기로 했다.

마침 선생님이 계시니 오히려 좋아.

일단 냅다 화면을 만들어보기로 했으나 음… 어디다 뭘 해야할지, 심지어 이분의 리액트 철학이 담긴 프로젝트라 더더욱 모르곘더라. 공식문서부터 다시 읽어보기로 했다.

아니 근데 리액트 공식문서가 이렇게 잘 되어있었는지는 몰랐다. 공식 문서의 한 파트 이름이 “Thinking In React”인데, 이름 그대로다.

무튼 공식문서 리액트 시작하는 분들께 추천을 드리며, 숭숭 읽다가도 아 이건 기억해야겠다, 얘는 뭔소리지 했던 것들을 차례로 정리해보려고 한다.


Part 1. React가 화면을 그리는 법: 렌더링과 커밋, 그리고 지역 변경

React는 데이터가 변경될 때마다 화면 전체를 새로 그리는 비효율을 막기 위해 렌더링과 커밋 단계를 명확히 구분한다. 이 과정에서 DOM이 어떻게 보존되는지, 그리고 렌더링 도중 허용되는 변수 조작은 무엇인지 알아본다.

1. DOM 변경의 최소화: 커밋 단계

[질문: 리렌더링이 일어나면 화면이 새로고침 되는가?]

컴포넌트가 리렌더링된다는 것은 해당 함수가 다시 호출된다는 뜻이다. 그렇다면 함수가 다시 실행될 때마다 내부의 모든 DOM 요소들이 파괴되고 다시 생성되는 것인지, 만약 그렇다면 사용자가 <input> 태그에 입력 중이던 텍스트는 리렌더링 순간 사라져야 하는 것이 아닌지 의문이 생긴다.

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input /> {/* 여기에 글을 쓰고 있어도 time이 바뀔 때 사라지지 않는다 */}
    </>
  );
}

[답변: 필요한 최소한의 작업만 수행한다]

React는 초기 렌더링 시에는 생성한 모든 DOM 노드를 appendChild() DOM API를 사용해 화면에 그린다. 하지만 이후 데이터 변경으로 인한 리렌더링 시에는 방식이 다르다. React는 렌더링을 통해 계산된 결과(새로운 가상 DOM)와 현재 화면(이전 DOM)을 비교한다.

위 코드에서 time이 변경되어 컴포넌트가 다시 호출되더라도, React는 <input> 태그가 이전과 동일한 위치에 동일한 타입으로 존재함을 확인한다. 따라서 <input>의 DOM 노드는 건드리지 않고 그대로 둔다. 반면 <h1> 태그 내부의 텍스트는 변경되었으므로, 해당 텍스트 노드만 새로운 시간으로 업데이트한다. 이 단계를 커밋(Commit)이라 하며, React는 변경 사항이 있는 부분만 골라 DOM에 적용한다.

[텍스트가 사라지지 않는 이유]

결국 리렌더링은 ‘화면 전체 새로고침’이 아니라 ‘변경된 부분 찾기(Diffing)’ 과정이다. <input> 자체는 변경 대상이 아니므로, 브라우저가 관리하는 <input>의 내부 상태(사용자가 입력한 텍스트)는 React의 간섭을 받지 않고 그대로 유지된다.


2. 재귀적 렌더링

[질문: 렌더링 과정은 구체적으로 어떻게 일어나는가?]

“렌더링은 재귀적으로 일어난다”는 표현이 등장한다. 단순히 컴포넌트를 호출하는 것과 재귀적으로 호출하는 것에 어떤 차이가 있는지, 그리고 React가 화면에 표시할 내용을 어떻게 파악하는지 이해가 필요하다.

export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image /> {/* 컴포넌트 */}
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return <img src="..." />; // HTML 태그
}

[답변: HTML 태그가 나올 때까지 파고든다]

React에서 렌더링은 컴포넌트(함수)를 호출하는 것이다. Gallery 컴포넌트를 렌더링할 때, React는 반환값에 포함된 <section><h1>은 HTML 태그임을 인지한다. 하지만 <Image />는 React 컴포넌트이므로, 이것이 구체적으로 어떤 UI를 그려야 하는지 아직 알 수 없다.

따라서 React는 <Image /> 컴포넌트를 호출(렌더링)한다. 그 결과 <img> 태그가 반환되면 비로소 화면에 무엇을 그려야 할지 알게 된다. 만약 Image가 또 다른 컴포넌트를 반환한다면, React는 더 이상 컴포넌트가 나오지 않고 순수 HTML 태그만 남을 때까지 이 과정을 반복한다. 이것이 재귀적 렌더링이다.

[계산과 반영의 분리]

이 재귀적 호출 과정은 오직 “무엇을 그려야 할지 계산하는 단계”다. React는 컴포넌트 트리를 끝까지 순회하며 정보를 수집할 뿐, 이 과정 중에 화면(DOM)을 실제로 수정하지는 않는다. 모든 계산이 끝난 후, 앞서 설명한 커밋 단계에서 변경 사항을 일괄적으로 적용한다.


3. 지역 변경

[질문: 렌더링 중에 변수를 수정해도 되는가?]

React의 컴포넌트는 순수해야 한다고 한다. 즉, 입력값이 같으면 항상 같은 결과를 반환해야 하며, 외부의 상태를 변경해서는 안 된다. 그런데 문서에서는 “지역 변경은 괜찮다”고 한다. 변경은 안 된다고 했는데 지역 변경은 허용된다는 것이 모순처럼 느껴진다.

[답변: 생성과 수정이 내부에서 종결된다면 허용된다]

“변경”이 문제가 되는 상황은 이미 존재하는 외부 변수나 객체의 값을 렌더링 도중에 수정할 때다. 이는 컴포넌트의 순수성을 해친다. 하지만 컴포넌트 함수 내부에서 생성한 변수나 객체를 수정하는 것은 다르다.

export default function TeaSet() {
  let cups = []; // 1. 렌더링 도중에 내부에서 생성됨
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />); // 2. 생성된 배열을 수정(Mutation)함
  }
  return cups; // 3. 결과 반환
}

위 코드에서 cups 배열은 TeaSet 함수가 호출될 때 생성된다. push를 사용하여 배열의 내용을 변경하지만, 이 배열은 함수 내부에서만 존재하며 아직 함수 외부의 어떤 코드도 이 배열을 참조하고 있지 않다. 즉, 이 변경은 컴포넌트 밖의 세상에 아무런 영향을 주지 않는다.

[안전함의 기준]

지역 변경의 핵심은 “격리”다. 함수가 실행되는 동안 만들어진 변수는, 그 함수가 값을 반환(return)하기 전까지는 외부에서 접근할 수 없다. 따라서 내부에서 마음껏 수정하더라도 외부 시스템의 상태를 꼬이게 만들 위험이 없다. React는 이러한 안전한 범위 내에서의 데이터 조작을 허용하여 개발 편의성을 제공한다.


© 2022. All rights reserved.

Powered by Hydejack v9.2.1