02장 리액트 핵심 요소 깊게 살펴보기
React Deep Dive
- add table of contents
2.1 JSX란?
- JS 내부에 표현하기 까다로운 XML Style Tree구문 작성 돕는 문법
- React 고유 전유물도 아님 (리액트(메타)가 소개하긴 함)
자바스크립트 표준(ECMAScript)구문이 아님
- SO (babel) 트랜스파일러를 거쳐야 자바스크립트 런타임에서 실행되는 코드로 변환됨
@babel/plugin-transform-react-jsx
플러그인을 통해 JSX 구문을 자바스크립트가 이해할 수 있는 형태로 변환한다.
- HTML 과 구분 위해 대문자로 시작
JSXElement
: JSX 가장 기본 구성 요소, (similar to HTML) 아래 중 하나여야 함JSXOpeningElement
,JSXClosingElement
: (<JSXElement JSXAttributes></JSXElement>
)JSXAttributes
,JSXChildren
은 선택사항JSXSelfClosingElement
: 요소 스스로 종료 (<JSXElement JSXAttributes />
), no childrenJSXFragment
: </> 단독 사용 (self closing) 불가능 (<>{JSXChildren}</>
)
JSXElementName
: 요소 이름으로 사용 가능JSXIdentifier
: JSX내부에서 사용할 수 있는 식별자JSXNamespacedName
: =JSXldentifier:JSXldentifier
의 조합: (식별자를 이어주는 것, 한번만 잇기 가능)JSXMemberExpression
:JSXldentifier.JSXldentifier
의 조합 (여러개 잇기 가능, butJSXNamespacedName
와는 못 잇는다)
JSXAttributes
:JSXElement
에 부여할 수 있는 속성 (없어도 에러 발생 X => 필수X)
JSXSpreadAttributes
- JS의 전개 연산자와 동일 역할<JSXElement {...AssignmentExpression} />
AssignmentExpression
: 키에 흘당할 수 있는 값 (객체, 조건문 표현식, 화살표 함수, 할당식 등 모든 표현식 가능)JSXAttributeName
:JSXAttributeValue
: 속성을 나타내는 키-값 쌍으로 표현함
JSXChildren
:JSXElement
의 자식 값- JSX는 속성을 가진 트리 구조를 나타내기 위해 만들어졌기 때문에 부모-자식 관계를 표현할 수 있음
JSXStrings
: 자바스크립트 코드 안에서 HTML 요소를 표현하는 방식
2.2 가상 DOM과 React Fiber
2.2.1 DOM 과 브라우저 렌더링 과정
- DOM + CSSOM \(\rightarrow\) Render Tree
브라우저가 사용자가 요청한 주소를 방문해 HTML파일을 다운로드 한다
- 브라우저의 렌더링 엔진: HTML 파싱 \(\longrightarrow\) create DOM TREE
- CSS 파일도 보이면 해당 CSS 파일도 다운
- 브라우저의 렌더링 엔진: CSS 파싱 \(\longrightarrow\) create CSSOM TREE
- (2)의 DOM 노드 순회 중 사용자가 눈에 보이는 노드만 방문 (ex:
display: none
은 방문 X) 해당 노드에 대한 CSSOM 정보 찾고 노드에 적용.
- a)
Layout (reflow) : 각 노드가 브라우저 화면의 어느 좌표에 나타나야 하는지 계산하는 과정 - b)
Painting : 레이아웃 단계를 거친 노드에 색과 같은 유효한 모습을 그리는 과정
- a)
- DOM and CSSOM trees combine to form render tree
- Render tree contains only the nodes required to render the page
- Layout computes the exact position and size of each object
- Painting phase takes final render tree nad renders the pixels to screen
2.2.2 Virtual DOM
모든 렌더링 과정을 브라우저에서 하는 게 아닌 메모리에서 계산하고 최종 결과를 브라우저 DOM에 보여준다면, 렌더링 과정을 최소화하여 비용을 줄일 수 있고, 브라우저와 개발자의 부담을 덜 수 있다.
브라우저가 웹페이지를 렌더링하는 과정은 매우 복잡하고 많은 비용이 든다
- interaction 추가 -> changes DOM AFTER 렌더링
- 특히 SPA에서는 하나의 페이지에서 계속해서 요소의 위치를 재계산 -> DOM 변경 비용 UP
변경사항 추적 대신 최종 결과물 하나만 확인 필요
Virtual DOM: 웹페이지가 표시해야 할 DOM을 메모리에 저장
- 실제 변경에 대한 준비가 완료되었을 때 실제 브라우저 DOM에 반영
- DOM 계산을 브라우저가 아닌 메모리에서 계산하여, 실제로는 여러 번 발생했을 렌더링 과정을 최소화
⚠️ 가상 DOM이 일반 DOM을 관리하는 브라우저보다 빠른 것은 아님!
- 대부분의 상황에서 웬만한 애플리케이션을 만들 수 있을 정도로 충분히 빠르다는 것
2.2.3 React Fiber: 가상 DOM을 위한 아키텍쳐
🪡 React Fiber: 가상 DOM과 렌더링 과정 최적화 가능케 함
- 평범한 JS Object임
Managed by
Fiber Reconciler - 가상 DOM과 실제 DOM 비교하여 변경사항 수집
- 차이 있을 시 -> 변경작업을 보유한 fiber기준으로 화면에 렌더링 요청
- fiber: 하나의 작업 단위로 구성
- 리액트는 위 Fiber들을 하나씩 처리 후 (
finishedWork()
)로 마무리finishedWork()
Commit 후: 실제 browser DOM에 가시적인 변경사항으로 변경
- fiber vs react element
- 리액트 요소: 렌더링 발생 시 새롭게 생성
- fiber: 컴포넌트 최초 마운트 시점에 생성, 이후는 최대한 재사용됨
React Fiber Tree
React 내부에 두 개 존재
- 현재 모습을 담은 파이버 트리
- 작업 중인 상태를 나타내는
workInProgress
트리
double buffering 기술 통해 변경사항을 렌더링에 반영한다
double buffering: fiber 작업 완료 후 단순히 포인터만 변경하여 workInProress 트리를 현재로 변경하는 기술
Fiber 작업 순선
beginWork()
: 더 이상 자식이 없는 파이버를 만날 때 까지 (tree-like)completeWork()
로 파이버 작업 완료- 형제가 있다면 형제로 넘어가기
- 2, 3번이 return으로 돌아가 본인 작업 완료 표명
2.2.4 Fiber과 가상 DOM
- 파이버: react component 에 대한 정보를 1:1 보유 (fiber works asyncrhonously within react architecture)
- but in real browser arch, DOM must be applied synchronously
- => work in memroy FIRST, and THEN apply in real browser
🎯 Virtual DOM, React’s GOAL: 브라우저의 DOM을 빠르게 draw + 반영이 아니라
값으로 UI 표현하는 것
흐름을 효율적으로 관리하기 위한 메커니즘
2.3 클래스 컴포넌트와 함수형 컴포넌트
2.3.1 클래스 컴포넌트
생명주기 메서드
mount
\(\rightarrow\)update
\(\rightarrow\)unmount
render
: inmount
andupdate
phase에 호출 가능- PURE ONLY
- render에 setState 사용 \(\rightarrow\) infinite loop
componentDidMount
:mount
phase에 호출- 함수 내에서 setState 사용 가능
componentDidUpdate
:update
직후에 바로 실행됨- setState 사용 가능, but 조건문으로 감싸지 않으면 infinite loop
componentWillUnmount
:unmount
또는 더 이상 사용되지 않기 직전에 호출componentDidMount
에서 등록한 이벤트 해제, 타이머 해제 등- 메모리 누수 등 불필요한 작동을 막기 위한 클린업 함수 호출 여기다 쓰면 됨
- setState 사용 불가
shouldComponentUpdate
:update
직전에 호출- 컴포 리렌더링 방지 조작 위해 사용하지만 이는 조작이니
- 성능 최적화 상황에서만 사용 권장
static getDerivedStateFromProps
:render()
직전에 호출- props로부터 state를 동기적으로 업데이트
- (오고 있는 props 바탕으로 현재의 state를 변경하고 싶을 때 사용)
- static이라서 this 사용 불가
- 반환 객체 = state 로 업데이트
getSnapshotBeforeUpdate
: DOM 업데이트 직전에 호출- DOM 업데이트 직전에 호출되어야 하는 작업을 수행 (ex: 스크롤 위치 유지, 윈도우 크기 조절 등)
componentDidUpdate
대체 가능- 반환값 =
componentDidUpdate
의 3번째 인자로 전달
Medium: React Version 16.4 (버전별로 차이 있음)
에러 발생 시
getDerivedStateFromError()
: 자식 컴포넌트에서 에러 발생 시 호출- 에러 발생 시 호출되어야 하는 작업을 수행
componentDidCatch()
대체 가능- react hook으로 구현 안되어 있어서 클래스 컴포넌트에서만 사용 가능 (2023년 9월 기준)
- state값 반환, side effect X
componentDidCatch()
- getDerivedStateFromError() 에서 에러 잡고 state 결정 한 이후 실행
- 부수 효과 가능
클래스 컴포넌트의 한계
데이터 흐름 어려움, 내부 로직 재사용 어려움, 코드 복잡성 및 사이즈 증가..etc
2.3.2 함수형 컴포넌트
- render 내부에서 필요 함수 선언 시
this
바인딩 필요 X- return 문에서 바로 사용 가능 (props, state 접근 가능)
- state는 객체가 아닌 각각의 원시값으로 관리 (사용 용이)
Comparison
클래스 컴포넌트 | 함수형 컴포넌트 | |
---|---|---|
lifecycle | render 메소드 있는 React.Component 상속받아 구현 | props만 받아 React 요소만 반환 |
useEffect | componentDidMount, componentDidUpdate, componentWillUnmount | useEffect |
렌더링 값 | 시간의 흐름에 따라 변화하는 this기준으로 렌더링 | props, state기준으로 렌더링 |
2.4 렌더링은 어떻게 일어나는가?
2.4.1 렌더링
- React Application Tree 내 모든 컴포넌트들이 가지고 있는 props와 state값을 기반으로 아래 내용을 계산
- 어떻게 UI를 구성할 것인가
- 어떤 DOM 결과를 브라우저에 제공할 것인가
2.4.2 렌더링 발생 시나리오
- 최초 렌더링
리렌더링
- 클래스 컴포넌트:
setState
,forceUpdate
- 함수 컴포넌트 :
useState
의 setter,useReducer
의 dispatch - 컴포넌트의
key
prop 변경,props
변경 - 부모 컴포넌트 리렌더링 (자식도 무조건 리렌더링)
- 클래스 컴포넌트: