본문 바로가기
Front-End Study/모던 리액트 딥다이브 스터디

모던 리액트 딥다이브 - 5회차 [2-1, 2-2]

by 코딩기 2024. 6. 10.
728x90

2. 리액트 핵심 요소 깊게 살펴보기

리액트를 이루는 핵심 개념들이 자바스크립트를 토대로 어떻게 동작하는지 알아보자고~

2.1 JSX란?

✨ JSX는 페이스북이 소개한 새로운 구문이지만, 리액트의 전유물만은 아니다.

  • 종속적이지 않고, 독자적이며, JS의 일부 또한 아니므로 트랜스파일러를 거쳐야 JS가 이해 가능한 문법으로 완성되며, XML 스타일의 트리 구문 작성에 큰 도움을 준다.

 

2.1.1 JSX의 정의


 

🔖 JSXElement, JSXAttributes, JSXChildren, JSXStrings 라는 4가지 컴포넌트를 기반으로 한다.

🏷️ JSXElement

  • 가장 기본적인 요소로, HTML의 요소와 비슷한 역할이다. 다음 조건들을 하나라도 충족해야 한다.
    1. JSXOpeningElement : 일반적으로 볼 수 있는 요소, 시작하는 요소이다.
    2. JSXClosingElement : 종료를 알리는 요소로, OpeningEle(간추려서)로 시작 시 이걸로 끝내야 한다. (한 쌍)
    3. JSXSelfClosingElement : 요소가 시작되고 스스로 종료되는 형태이다. </script>와 동일한 형태이다.
    4. JSXFragment : 아무 요소가 없는 형태로 <> element.. </> 형태로 사용한다.

 

👍 알고 가기 좋은 상식!

  • 요소명은 '대문자'로 시작해야 하며, 그 이유는 HTML 구문과 합쳐진 형태의 JSX에서 태그와 컴포넌트를 구분하기 위함이다.

 

JSXElementName

  • JSXElement의 요소 이름으로 사용 가능한 것들을 의미한다.
    • JSXIdentifier : JSX 내부에서 사용 가능한 식별자를 의미 (JS 식별자 규칙과 동일)
      // 가능
      function valid() {
        return <$></$>
      }
      // 불가능
      function invalid () {
        return <1></1>
      }
    • JSXNamespacedName : 서로 다른 식별자를 이어주는 것도 하나의 식별자로 판별 (두개 이상 X)
    // 가능
    function valid() {
      return <foo:bar></foo:bar>;
    }
    • JSXMemberExpression : '.'를 통해 서로 다른 식별자를 이어주는 것을 의미 ('.' 여러개 사용O, NameSpacedName 과 섞어쓰기 X)
    // 가능
    function valid() {
      return <foo.bar></foo.bar>;
    }

JSXAttribute

  • JSXElement에 부여 가능한 속성을 의미한다. (필수값X, 존재하지 않을 시 에러X)
    • JSXSpreadAttributes : JS의 전개 연산자와 동일한 역할을 한다. (보통 객체 형태의 Prop의 키들을 한번에 내릴 때 많이 사용했다.)
    // 단순히 값 뿐만 아니라 모든 표현식이 존재할 수 있다.
    function valid() {
      const attribute = {
        className: "Div",
        style: { display: "flex" },
      };
      return <div {...attribute}>hi~</div>;
    }
    • JSXAttribute : 속성을 나타내는 키와 값으로 표현한다. (키는 JSXAttributeName, 값은 JSXAttributeValue로 불림)
    function Child1({ text }) {
      return <p>{text}</p>;
    }
    
    function Child2({ attribute }) {
      return <div>{attribute}</div>;
    }
    
    function valid() {
      // 문자열
      return <Child1 text="text" />;
    
      // AssignmentExpression : 값 할당에 사용하는 표현식
      return <Child1 text={text} />;
    
      // JSXElement : 다른 JSX요소도 할당 가능
      return <Child attribute={<div>hi</div>} />;
    }

JSXChildren

  • JSXElement의 자식을 나타낸다. 기본적으로 속성을 가진 트리 구조를 나타내기 위해 만들었으므로 Children 또한 존재한다.
    • JSXChild : 기본 단위이며, 존재하지 않아도 상관없다. (필요 개수의 기본값 : 0)
    function valid() {
      // 문자열
      return <div>{"hi"}</div>;
    
      // JSXElement & JSXFragment
      return <div>{<p>hi~</p>}</div>;
    
      // JSXChildExpression : JS의 표현식 또한 할당 가능
      return <div>{(() => "foo")()}</div>;
    }

JSXStrings

  • JSXAttributeValueJSText는 HTML과 JSX 사이의 복붙을 쉽게 할 수 있도록 설계되어 있다. HTML에서 사용 가능한 문자열은 JSX에서도 모두 사용 가능하며, 이는 의도적인 설계이다. ("" or '')
    • 다만 JS에서는 이스케이프 문자( "\" )가 특수문자 처리에 사용되므로 HTML과 다르게 따로 설정해야 하는 부분은 있다.
      function valid () {
        // 가능
        return <div>\</div>
    
        // 불가능
        let escape = "\";
      }

 

2.1.2 JSX 예제


🔖 유효 JSX 구조를 사용한 예제 알아보기

// 기본
const ComponentA = <A>반갑다잉</A>; // or <A />

// 전개 연산자를 통한 속성 넣기
const ComponentB = <A {...attribute} />;

// 속성 및 속성값 지정
const ComponentC = <A requied={false} />;

// JSX 요소 넣기
const ComponentD = (
  <A>
    <B text="어렵다" />
  </A>
);

// {}를 통한 요소나 표현식 넣기
const ComponentE = (
  <A>
    <B children={<>어려워</>} />
  </A>
);

// 여러개의 자식 넣기
const ComponentF = (
  <A>
    흑흑
    <B text="엉엉" />
  </A>
);

 

2.1.3 JSX는 어떻게 자바스크립트에서 변환될까?


🔖 JSX 문법을 변환해주는 바벨 플러그인과 함께 알아보자

  • JSX 코드 예시
  • const ComponentA = <A required={true}>Hi!</A>; const ComponentB = ( <A> <B text="Hi!" /> </A> );
  • JS로 트랜스파일링
  • var ComponentA = React.createElement(A, { required: true }, "Hi!"); var CoponenetB = React.createElement(A, null, React.createElement(B, { text: "Hi" }));

🫨 추가적으로 찾아본 부분

  • 확실히 바벨 등의 트랜스파일러를 통해 JSX가 JS로 변환된다는 것을 알 수 있었고, 뭔가 조금 더 우리가 사용하는 코드로 예시를 보고 싶어서 찾아봤다.
  • 링크 : JSX와 Babel

 

2.1.4 책 정리 + 주관적인 정리


🔖 책 정리

  • JSX문법에 있으나 실제로 리액트에서 사용하지 않는 문법도 있다. (하지만 리액트에서지 다른 곳에서는 아닐 수도 있다.)
    • JSXNamespacedName
    • JSXMemberExpression
  • HTML과 같은 트리 구조를 JS 내에서 표현할 수 있어 매우 유능한 문법이다.
  • 하지만 기존 문법과 뒤섞여 가독성을 해칠 수도 있다.
  • 적어도 JSX가 어떻게 변환되는지, 어떤 결과를 만드는지 알아두어야 한다.

 

🏷️ 주관적인 정리

  • 평소에 JSX를 당연하게 사용만 해왔지, 이게 어떤 식으로 변환 및 재구성되어 브라우저에서 동작하는지에 대한 생각은 해보지 못했던 것 같다.
  • 구현에만 급급하지 말고, 항상 효율적으로 다양한 고민을 해야 할 듯하다.. (비용, 효율성, 구조, 간결한 코드 로직 등)

 

2.2 가상 DOM과 리액트 파이버

✨ 가상 DOM은 리액트의 대명사라고 해도 좋을 만큼 많이 알려져 있다. 가상 DOM의 이점과 주의할 점을 알아보자

2.2.1 DOM과 브라우저 렌더링 과정


🔖 DOM(Document Object Model)은 무엇인가?

  • 기본 브라우저 렌더링 과정

  1. 브라우저가 사용자 요청 주소의 HTML 파일을 다운로드 및 파싱하여 DOM노드 트리 작성
  2. CSS 파일이 있다면 같이 다운로드 및 파싱하여 CSS노드 트리(CSSOM) 작성
  3. 브라우저가 DOM 노드를 순회하며 사용자 눈에 보이는 것만 방문하여 분석
  4. 눈에 보이는 노드를 대상으로 CSSOM 정보를 찾고 스타일을 노드에 적용(레이아웃과 페인팅)

 

2.2.2 가상 DOM의 탄생 배경


🔖 현대의 웹은 단순히 콘텐츠를 보여주기 위함이 아닌, 사용자가 맛보고 즐기는 형태이다.

  • 하지만 이 때문에 단순히 콘텐츠를 보여줄 때보다 다양한 상황이 발생하며 (요소의 노출 여부 변경, 사이즈 변경 등), 이는 단순 페인팅 작업보다 훨씬 많은 비용을 지불해야한다. (레이아웃 변경 및 리페인팅)
  • 이러한 추가적인 렌더링 작업의 발생은 하나의 페이지에서 모든 작업이 이루어지는 SPA에서 더욱 부각된다.
  • 개발자의 관점에서 봤을 때, 하나의 상호작용으로 인해 페이지 내부가 변경되고, 이러한 변경 사항을 모두 추적하는 것은 쉽지 않다. 🤮

✅ 가상 DOM을 통해 이를 야무지게 해결해보자!

  • 가상 DOM이 웹페이지가 표시할 DOM을 일단 메모리에 저장, 준비가 완료되면 실제 DOM에 반영한다.
  • 따라서 조금의 변경으로도 레이아웃과 리페인팅 되었을 요소의 작업을 최소화하고, 브라우저와 우리의 부담을 덜어준다. (고마운 친구)

 

2.2.3 가상 DOM을 위한 아키텍쳐, 리액트 파이버

🔖 가상 DOM을 만드는 과정을 리액트가 어떻게 처리하는지 알아보자!

🏷️ 리액트 파이버란?

  • 리액트가 관리하는 JS의 객체로, 파이버 재조정자에 의해 관리되며 가상과 실제 DOM을 비교해 변경 사항 수집 및 차이가 있을 시 재조장자가 파이버를 기준으로 브라우저에 렌더링을 요청한다.
    • 작업을 작은 단위로 쪼개고 우선순위를 매김
    • 작업을 일시중지 및 다시시작 가능
    • 이전의 작업 재사용 및 불필요 시 폐기

👍 모든 과정은 비동기로 일어난다. (동기적으로 작업할 경우 너무나도 비효율적)

 

🏷️ 재조정(Reconcilation)

  • 리액트가 변경 부분 결정을 위해 한 트리를 다른 트리와 비교하는 알고리즘 (선언적 개발의 핵심 개념)
  • 리액트가 전체 앱을 리렌더링 시 뛰어난 성능을 유지할 수 있도록 해준다.
    • 컴포넌트 type이 다르면 실질적으로 다른 트리를 생성한다고 가정한다. 리액트는 이를 구분하지 않고, 이전의 트리를 교체한다.
    • 목록은 key를 통해 구분되기 때문에, key는 유니크할 필요가 있다.
    • 리액트는 재조정과 렌더링이 별개의 단계이므로, 재조정의 과정을 거쳐 변화된 요소들의 계산을 때린 뒤, 렌더링하여 앱을 실제로 업데이트 한다.

 

🏷️ 파이버의 구현

  • 파이버는 이러한 재조정의 단계를 재구현하여, 스케줄링형 프로그래밍의 이점을 잘 활용할 수 있도록 돕는다.
  • 하나의 작업 단위로 구성, 작업 단위를 처리하고 finishWork() 라는 작업으로 마무리, 이를 커밋해 브라우저 DOM에 가시적 변경 사항을 구현
    1. 렌더 단계에서 노출되지 않는 비동기 작업 수행 (우선순위 지정, 재사용 및 폐기, 파이버 작업 등)
    2. DOM에 변경사항을 구현하기 위해 commitWork() 실행 (동기식, 중단 X)

 

🏷️ 파이버의 구조

  • tpye & key : 재조징시 파이버가 재사용될 수 있는지 판단할 때 사용
  • child & sibling : 파이버의 재귀적 트리 구조를 묘사하는 개념
// Parent 컴포넌트의 child 파이버는 Child1, 2 컴포넌트이다.
// Child1, 2는 각자의 sibling 파이버이다.
function Parent() {
  return (<Child1 />), (<Child2 />);
}
  • return : 현재의 파이버를 처리 후 반환해야 하는 파이버이다.
// Child1, 2 컴포넌트의 return 파이버는 Parent 컴포넌트이다.
function Parent() {
  return (<Child1 />), (<Child2 />);
}
  • pendingProps & memoizedProps : 함수의 argumentprops가 시작할 때와 끝날 때 설정된다. 이를 통해 파이버의 재사용 여부를 판별한다. (React.memo의 기능 구현 또한 이 개념으로 이루어짐)
  • pendingWorkPriority : 파이버가 나타내는 작업들의 우선순위
  • alternate : 파이버는 Current Fiber(현재의 렌더링된 상태)와 Alternate Fiber(이전의 파이버)를 가지며, 이를 서로 연결하는 것이 alternate이다.
  • output : alternate를 통해 비교되어 업데이트된 렌더 트리와 그에 따른 효과 리스트, 이 과정을 통해 UI가 업데이트되고 변경 사항이 반영된다.

 

< 간략한 전체 과정 >

  1. 업데이트 과정:
    • 리액트가 컴포넌트를 업데이트할 때, 새로운 work-in-progress fiber가 생성
    • 이 새로운 파이버는 기존 current fiber의 복사본으로 시작되며, alternate 속성을 통해 이전 파이버와 연결
  2. 작업 진행:
    • 작업이 진행되면서 리액트는 pendingProps와 memoizedProps를 사용하여 변경 사항을 계산하고, 필요한 업데이트를 적용
    • 이 작업의 결과물은 work-in-progress fiber에 저장되며, 이는 곧 "output"으로 간주
  3. 커밋 단계:
    • 모든 변경 사항이 완료되면, work-in-progress fiber가 새로운 current fiber로 채택
    • 이전 current fiber는 이제 alternate가 되며, 새로운 current fiber와 연결

 

 

2.2.4 파이버와 가상 DOM


🔖 파이버는 리액트 컴포넌트에 대한 정보를 1:1로 가지고 있고, 비동기적으로 실행

🏷️ 하지만 실제 브라우저 DOM 반영은 동기적이며, 메모리상에서 이를 수행하여 최종 결과물만 반영

✅ 따라서 가상 DOM과 파이버는 동일 개념이 아니며, 가상 DOM은 리액트가 렌더링하는 방식의 한 과정일 뿐이다.

 

2.2.5 책 정리 + 주관적인 정리


🔖 책 정리

  • 가상 DOM과 파이버는 단순히 브라우저 DOM을 변경하는 작업보다 빨라서 만들어진 것이 아닌 개발자 대신 파이버와 재조정자가 효율적으로 알고리즘을 통해 관리해주는 것이다.
  • 따라서 이는 대규모 웹 앱을 효율적으로 유지보수 및 관리하는 데에 큰 도움을 준다.
  • UI를 문자열이나 배열 등과 마찬가지로 값으로 관리하여 효율성을 극대화 시키는 메커니즘이라고 볼 수 있다.

🏷️ 주관적인 정리

  • 단순히 가상 DOM과 실제 DOM의 비교를 통해 화면을 렌더링 한다고만 생각했는데, 조금 더 딥하게 심화적인 개념을 공부할 수 있었다.
  • 확실히 어려운 개념이고 지금은 조금 얕게 공부했다고 생각하지만 나중에 조금 더 공부해보고 싶다는 생각이 들었다.