2. 리액트 핵심 요소 깊게 살펴보기
리액트를 이루는 핵심 개념들이 자바스크립트를 토대로 어떻게 동작하는지 알아보자고~
2.1 JSX란?
✨ JSX는 페이스북이 소개한 새로운 구문이지만, 리액트의 전유물만은 아니다.
- 종속적이지 않고, 독자적이며, JS의 일부 또한 아니므로 트랜스파일러를 거쳐야 JS가 이해 가능한 문법으로 완성되며, XML 스타일의 트리 구문 작성에 큰 도움을 준다.
2.1.1 JSX의 정의
🔖 JSXElement
, JSXAttributes
, JSXChildren
, JSXStrings
라는 4가지 컴포넌트를 기반으로 한다.
🏷️ JSXElement
- 가장 기본적인 요소로, HTML의 요소와 비슷한 역할이다. 다음 조건들을 하나라도 충족해야 한다.
- JSXOpeningElement : 일반적으로 볼 수 있는 요소, 시작하는 요소이다.
- JSXClosingElement : 종료를 알리는 요소로, OpeningEle(간추려서)로 시작 시 이걸로 끝내야 한다. (한 쌍)
- JSXSelfClosingElement : 요소가 시작되고 스스로 종료되는 형태이다.
</script>
와 동일한 형태이다. - 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
JSXAttributeValue
와JSText
는 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)은 무엇인가?
- 기본 브라우저 렌더링 과정
- 브라우저가 사용자 요청 주소의 HTML 파일을 다운로드 및 파싱하여 DOM노드 트리 작성
- CSS 파일이 있다면 같이 다운로드 및 파싱하여 CSS노드 트리(CSSOM) 작성
- 브라우저가 DOM 노드를 순회하며 사용자 눈에 보이는 것만 방문하여 분석
- 눈에 보이는 노드를 대상으로 CSSOM 정보를 찾고 스타일을 노드에 적용(레이아웃과 페인팅)
- 링크 : 브라우저 로딩 과정
2.2.2 가상 DOM의 탄생 배경
🔖 현대의 웹은 단순히 콘텐츠를 보여주기 위함이 아닌, 사용자가 맛보고 즐기는 형태이다.
- 하지만 이 때문에 단순히 콘텐츠를 보여줄 때보다 다양한 상황이 발생하며 (요소의 노출 여부 변경, 사이즈 변경 등), 이는 단순 페인팅 작업보다 훨씬 많은 비용을 지불해야한다. (레이아웃 변경 및 리페인팅)
- 이러한 추가적인 렌더링 작업의 발생은 하나의 페이지에서 모든 작업이 이루어지는 SPA에서 더욱 부각된다.
- 개발자의 관점에서 봤을 때, 하나의 상호작용으로 인해 페이지 내부가 변경되고, 이러한 변경 사항을 모두 추적하는 것은 쉽지 않다. 🤮
✅ 가상 DOM을 통해 이를 야무지게 해결해보자!
- 가상 DOM이 웹페이지가 표시할 DOM을 일단 메모리에 저장, 준비가 완료되면 실제 DOM에 반영한다.
- 따라서 조금의 변경으로도 레이아웃과 리페인팅 되었을 요소의 작업을 최소화하고, 브라우저와 우리의 부담을 덜어준다. (고마운 친구)
2.2.3 가상 DOM을 위한 아키텍쳐, 리액트 파이버
🔖 가상 DOM을 만드는 과정을 리액트가 어떻게 처리하는지 알아보자!
🏷️ 리액트 파이버란?
- 리액트가 관리하는 JS의 객체로, 파이버 재조정자에 의해 관리되며 가상과 실제 DOM을 비교해 변경 사항 수집 및 차이가 있을 시 재조장자가 파이버를 기준으로 브라우저에 렌더링을 요청한다.
- 작업을 작은 단위로 쪼개고 우선순위를 매김
- 작업을 일시중지 및 다시시작 가능
- 이전의 작업 재사용 및 불필요 시 폐기
👍 모든 과정은 비동기로 일어난다. (동기적으로 작업할 경우 너무나도 비효율적)
🏷️ 재조정(Reconcilation)
- 리액트가 변경 부분 결정을 위해 한 트리를 다른 트리와 비교하는 알고리즘 (선언적 개발의 핵심 개념)
- 리액트가 전체 앱을 리렌더링 시 뛰어난 성능을 유지할 수 있도록 해준다.
- 컴포넌트
type
이 다르면 실질적으로 다른 트리를 생성한다고 가정한다. 리액트는 이를 구분하지 않고, 이전의 트리를 교체한다. - 목록은
key
를 통해 구분되기 때문에,key
는 유니크할 필요가 있다. - 리액트는 재조정과 렌더링이 별개의 단계이므로, 재조정의 과정을 거쳐 변화된 요소들의 계산을 때린 뒤, 렌더링하여 앱을 실제로 업데이트 한다.
- 컴포넌트
🏷️ 파이버의 구현
- 파이버는 이러한 재조정의 단계를 재구현하여, 스케줄링형 프로그래밍의 이점을 잘 활용할 수 있도록 돕는다.
- 하나의 작업 단위로 구성, 작업 단위를 처리하고
finishWork()
라는 작업으로 마무리, 이를 커밋해 브라우저 DOM에 가시적 변경 사항을 구현- 렌더 단계에서 노출되지 않는 비동기 작업 수행 (우선순위 지정, 재사용 및 폐기, 파이버 작업 등)
- 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
: 함수의argument
인props
가 시작할 때와 끝날 때 설정된다. 이를 통해 파이버의 재사용 여부를 판별한다. (React.memo의 기능 구현 또한 이 개념으로 이루어짐)pendingWorkPriority
: 파이버가 나타내는 작업들의 우선순위alternate
: 파이버는Current Fiber
(현재의 렌더링된 상태)와Alternate Fiber
(이전의 파이버)를 가지며, 이를 서로 연결하는 것이alternate
이다.output
:alternate
를 통해 비교되어 업데이트된 렌더 트리와 그에 따른 효과 리스트, 이 과정을 통해 UI가 업데이트되고 변경 사항이 반영된다.
< 간략한 전체 과정 >
- 업데이트 과정:
- 리액트가 컴포넌트를 업데이트할 때, 새로운 work-in-progress fiber가 생성
- 이 새로운 파이버는 기존 current fiber의 복사본으로 시작되며, alternate 속성을 통해 이전 파이버와 연결
- 작업 진행:
- 작업이 진행되면서 리액트는 pendingProps와 memoizedProps를 사용하여 변경 사항을 계산하고, 필요한 업데이트를 적용
- 이 작업의 결과물은 work-in-progress fiber에 저장되며, 이는 곧 "output"으로 간주
- 커밋 단계:
- 모든 변경 사항이 완료되면, 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의 비교를 통해 화면을 렌더링 한다고만 생각했는데, 조금 더 딥하게 심화적인 개념을 공부할 수 있었다.
- 확실히 어려운 개념이고 지금은 조금 얕게 공부했다고 생각하지만 나중에 조금 더 공부해보고 싶다는 생각이 들었다.
'Front-End Study > 모던 리액트 딥다이브 스터디' 카테고리의 다른 글
모던 리액트 딥다이브 - 7회차 [3-1, 3-2, 3-3] (2) | 2024.06.19 |
---|---|
모던 리액트 딥다이브 6회차 [2-3, 2-4, 2-5] (0) | 2024.06.17 |
모던 리액트 딥다이브 - 4회차 [1-6, 1-7] (1) | 2024.06.08 |
모던 리액트 딥다이브 - 3회차 [1-3, 1-4, 1-5] (1) | 2024.06.03 |
모던 리액트 딥다이브 - 2회차 [1-1, 1-2] (0) | 2024.06.01 |