2.1-2.3 : 리액트 핵심 요소 (JSX, 가상DOM, 리액트 파이버, 클래스/함수 컴포넌트)
1. JSX?
자바스크립트 코드 내부에 html과 같은 트리구조를 가진 컴포넌트 표현 가능
자바스크립트 표준은 아님. 리액트가 등장하며, 메타에서 소개한 새로운 구문.
내장형 구문, 리액트에 종속적이지 않은 독자적인 문법
반드시 트랜스파일러를 거쳐야 자바스크립트 런타임이 이해할 수 있는 자바스크립트 코드가 됨.
(@babel/plugin-transform-react-jsx)
JSXElement, JSXAttributes, JSXChildren, JSXStrings 컴포넌트 기반으로 구성.
- JSXElement : JSX 구성 기본 요소, HTML의 요소와 비슷한 역할
-> 네가지 형태중에 하나여야 함
1) JSXOpeningElement : <JSXElement>
2) JSXClosingElement : </JSXElement >
3) JSXSelfClosingElement : <JSXElement />
4) JSXFragment : <> JSXChildren </>
+ 요소명을 대문자로 시작해야 하는 이유 : 리액트에서 html태그명과 사용자가 만든 컴포넌트 태그명을 구분짓기 위함.
- JSXAttributes : JSXElement에 부여할 수 있는 속성, 모든 경우에 필수 값은 아님.
- JSXChildren : JSXElement의 자식 값
- JSXStrings
2. 가상 DOM과 리액트 파이버
리액트는 실제 DOM이 아닌 가상 DOM을 운영함
1) DOM (Document Object Model)
웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담음.
** 렌더링 트리 만들어지는 과정
1) 브라우저가 사용자가 요청한 주소를 방문해 html파일을 다운받음
2) 브라우저의 렌더링 엔진이 html을 파싱해서 dom노드로 구성된 트리인 dom을 만듦.
3) css파일도 다운받음
4) 브라우저의 렌더링 엔진이 css을 파싱해서 css노드로 구성된 트리인 cssom을 만듦.
5) 브라우저가 dom노드를 순회하며, 사용자 눈에 보이는 노드를 방문함(display:none;은 방문하지 않음)
6) 눈에 보이는 노드를 대상으로 cssom정보를 찾고, css 스타일 정보를 노드에 적용
-> 레이아웃과 페인팅 작업이 이뤄짐.
(* 레이아웃 : 각 노드가 나타날 화면의 좌표 계산, 레이아웃을 거치면 페인팅도 반드시 거침.
* 페인팅 : 레이아웃 단계 거친 노드에 색과 같은 실제 모습을 그리는 과정)
* 가상 DOM 탄생 배경
DOM변경이 자주 일어나게 되면 하위 자식 요소들도 덩달아 변경되며 비용 증가
특히, 이런 추가 렌더링들은 SPA에서 더 많아짐.
하나의 페이지에서 계속해서 요소의 위치를 재계산 하므로 부담 비용이 커짐.
* 가상 DOM : 리액트가 관리하는 가상의 DOM
웹페이지가 표시해야할 DOM을 일단 메모리에 저장하고, 리액트가 실제 변경에 대한 준비가 완료됐을 때 실제 브라우저의 DOM에 반영
DOM 계산을 메모리에서 거치기 때문에 렌더링 과정 최소화 가능
2) 리액트 파이버
: 가상 DOM과 렌더링 최적화를 가능하게 해주는 자바스크립트 객체
파이버 재조정자가 관리.
파이버 재조정자는 가상DOM과 실제DOM을 비교해 변경 사항을 수집하고, 이 둘의 차이가 있다면 변경 관련 정보를 가진 파이버를 기준으로 화면에 렌더링을 요청함.
작업을 작은 단위로 쪼개고 우선 순위 매김, 작업을 일시중지하고 나중에 다시 시작, 이전 작업 재사용 하거나 폐기 하는 등의 일을 할 수 있음 -> 모두 비동기로 일어남.
+ 스택조정자 대신 파이버를 쓰는 이유
과거 리액트의 조정 알고리즘은 스택 알고리즘인데, 스택조정자는 동기식으로 작동한다.
자바스크립트는 싱글 스레드 언어이기 때문에, 동기작업이 중단 될 수 없어 비효율적.
그와 다르게, 파이버는 비동기적이고 작은 작업 단위로 구성됨.
파이버는 작업을 작은 단위로 나누어 처리할 수 있기 때문에, 리액트는 작업의 우선순위를 정해 중요한 작업을 먼저 처리하거나, 장시간 걸리는 작업을 중간에 중단하고 나중에 다시 처리하는 등의 유연한 작업 관리가 가능.
또, 리액트에서 파이버는 자바스크립트 객체로 나타나며, 컴포넌트의 마운트 시점에 생성되고 이후 재사용될 수 있음.
파이버는 컴포넌트의 상태가 변경되거나 생명주기 메서드가 실행되거나 DOM의 변경이 필요한 시점에 맞추어 동작
-> 불필요한 렌더링을 방지하고, 애플리케이션의 성능을 최적화
* 리액트 파이버 트리 : 리액트 내부에 2개가 존재.
하나는 현재 모습을 담은 파이버 트리 current, 다른 하나는 작업중인 상태를 나타내는 workInProgress트리
리액트 파이버의 작업이 끝나면, 리액트는 포인터만 변경함. (workInProgress트리를 현재 트리로 바꿈)
이 작업을 '더블 버퍼링'이라고 함.
* 리액트 파이버 트리 동작 과정
1) current는 UI렌더링을 위해 존재하는 트리, current를 기준으로 모든 작업이 시작됨.
2) 업데이트가 발생하면 파이버는 리액트에서 새로 받아온 데이터로 새로운 workInProgress트리를 빌드함.
3) workInProgress트리 빌드가 끝나면, 다음 렌더링에 workInProgress트리를 사용.
4) workInProgress트리가 UI에 최종적으로 렌더링 되어 반영되면, current가 workInProgress트리로 변경됨.
** 상태가 업데이트 될 때 일어나는 일
업데이트 되면, 새로운 workInProgress트리를 빌드.
최초 렌더링 시에는 모든 파이버를 새롭게 만들지만, 이후에는 파이버가 존재하므로 되도록 새로 생성하지 않고 기존 파이버에서 업데이트된 props를 받아 파이버 내부에서 처리.(내부 속성값 초기화 / 바꾸는 형태)
-> 계속해서 새로운 파이버 객체 만드는 것은 리소스 낭비이므로.
과거에는 이 작업들을 동기식으로 처리, 현재는 우선순위 부여해서 최적의 순위로 작업 가능!
💫 파이버는 리액트 아키텍쳐 내부에서는 비동기로 이뤄지지만,
실제 브라우저 구조인 DOM에서는 동기적으로 일어남.
-> 처리하는 작업이 많아 불완전하게 표시 될 수 있으므로, 메모리 상에서 먼저 수행해서 최종 결과물만 실제 브라우저 DOM에 적용
3. 클래스 컴포넌트와 함수 컴포넌트
1) 클래스 컴포넌트
i) 생명주기
마운트 (mount) : 컴포넌트가 생성되는 시점
업데이트 (update) : 이미 생성된 컴포넌트 내용이 변경되는 시점
언마운트 (unmount) : 컴포넌트가 더이상 존재하지 않는 시점
ii) 메서드
- render() : 클래스 컴포넌트의 유일한 필수 값
컴포넌트가 UI를 렌더링하기 위해 씀.
마운트와 업데이트 과정에서 일어남
항상 순수해야하고, 부수효과 없어야 함 -> 내부에서 this.setState() 사용 하면 안됨.
- componentDidMount() : 클래스 컴포넌트가 마운트 되면 즉시 실행.
render()와 다르게 this.setState()로 상태 변경 가능, 상태 변경 되면 즉시 렌더링 시도해서 사용자는 눈치 챌 수 없음.
성능문제 주의
- componentDidUpdate() : 컴포넌트 업데이트 일어난 이후 바로 실행.
state와 props 변화에 따라 DOM을 업데이트 하는 등에 쓰임.
this.setState() 사용 가능하나, 조건문 사용하지 않으면 무한 호출..
- componentWillUnmount() : 컴포넌트가 언마운트 되거나 더이상 사용되지 않기 직전 호출
메모리 누수나 불필요한 작동 막기 위한 클린업 함수 호출 위한 최적의 위치.
내부에서 this.setState() 사용 못함.
- shouldComponentUpdate() : props나 state 변화로 리렌더링 막고 싶을때 사용
this.setState()로 인해 리렌더링을 막음.
+ PureComponent는 얕은 비교만 수행하므로, state가 객체와 같은 복잡한 구조의 데이터 변경을 감지하지 못함.
iii) 한계
- 데이터의 흐름을 추적하기 어려움
- 기능이 많아질수록 컴포넌트 크기가 커짐
- 내부 로직 재사용이 어려움
- 코드 크기 최적화가 어려움
- 핫 리로딩이 상대적으로 불리함
(* 핫 리로딩 :코드에 변경 사항 생겼을 때, 앱을 다시 시작하는게 아니라 변경 코드만 업데이트해 변경 사항 빠르게 적용하는 기법)
2) 함수 컴포넌트
함수 컴포넌트는 사실 예전부터 있었지만, v16.8 전까지는 무상태 함수 컴포넌트라고 해서 상태 없이 요소를 정적으로 렌더링하는 것이 목적이었음.
v16.8부터 컴포넌트에서 사용 가능한 훅 등장.
3) 클래스 컴포넌트와 함수 컴포넌트 비교
- this 바인딩 주의(함수 컴포넌트는 그럴 필요 없음)
- state 값 (함수 컴포넌트는 원시값으로 관리되어 편하고, 객체도 관리 가능)
- 함수 컴포넌트에서 생명주기 메서드의 부재
- 렌더링 된 값 고정 유무(함수 컴포넌트는 고정, 클래스 컴포넌트는 항상 this로부터 props값 가져오므로 변경 가능)