JSX는 자바스크립트 내부에서 표현하기 까다로웠던 XML 스타일의 트리 구문을 작성하는 데 많은 도움을 주는 새로운 문법
2.1.1 JSX의 정의
JSX는 기본적으로 JSXElement, JSXAttributes, JSXChildren, JSXStrings라는 4가지 컴포넌트를 기반으로 구성
JSXElement
JSX를 구성하는 가장 기본 요소로, HTML의 요소(element)와 비슷한 역할
JSXAttributes
JSXElement에 부여할 수 있는 속성을 의미
JSXChildren
JSXElement의 자식 값
JSXText, JSXElement, JSXFragment가 올 수 있음
2.1.3 JSX는 어떻게 자바스크립트로 변환될까?
babel/plugintransform-react-jsx 플러그인을 통해 JSX 구문을 자바스크립트가 이해할 수 있는 형태로 변환
2.2 가상 DOM과 리액트 파이버
리액트의 가상 DOM이 무엇인지, 그리고 실제 DOM에 비해 어떤 이점이 있는지에 대해서 알아보자.
2.2.2 가상 DOM의 탄생 배경
가상 DOM은 웹페이지가 표시해야 할 DOM을 일단 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료됐을 때 실제 브라우저의 DOM에 반영
DOM 계산을 브라우저가 아닌 메모리에서 계산하는 과정을 한 번 거치게 된다면 실제로는 여러 번 발생했을 렌더링 과정을 최소화할 수 있고 브라우저와 개발자의 부담을 덜 수 있음
2.2.3 가상 DOM을 위한 아키텍처, 리액트 파이버
가상 DOM과 렌더링 과정 최적화를 가능하게 해주는 것이 바로 리액트 파이버(React Fiber)
파이버 재조정자(fiber reconciler)
가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하며, 만약 이 둘 사이에 차이가 있으면 변경에 관련된 정보를 가지고 있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할
리액트 파이버란?
리액트 파이버는 리액트에서 관리하는 평범한 자바스크립트 객체
작업을 작은 단위로 분할하고 쪼갠 다음, 우선순위를 매김
이러한 작업을 일시 중지하고 나중에 다시 시작할 수 있음
이전에 했던 작업을 다시 재사용하거나 필요하지 않은 경우에는 폐기할 수 있음
파이버는 일단 하나의 작업 단위로 구성
1) 렌더 단계에서 리액트는 사용자에게 노출되지 않는 모든 비동기 작업을 수행. 그리고 이 단계에서 파이버의 작업, 우선순위를 지정하거나 중지시키거나 버리는 등의 작업이 일어남
2) 커밋 단계에서는 DOM에 실제 변경 사항을 반영하기 위한 작업. commitWork()가 실행되는데, 이 과정은 앞서와 다르게 동기식으로 일어나고 중단될 수 없음
리액트 요소는 렌더링이 발생 할 때마다 새롭게 생성되지만 파이버는 가급적이면 재사용됨
리액트는 가상 DOM이 아닌 Value UI, 즉 값을 가지고 있는 UI를 관리하는 라이브러리
리액트의 핵심 원칙은 UI를 문자열, 숫자, 배열과 같은 값으로 관리한다는 것
리액트 파이버 트리
파이버 트리는 리액트 내부에서 두 개가 존재
하나는 현재 모습을 담은 파이버 트리이고, 다른 하나는 작업 중인 상태를 나타내는 workInProgress 트리
리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해 worklnProgress 트리를 현재 트리로 변경
2.2.4 파이버와 가상 DOM
리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이뤄짐
이러한 비동기 작업과 달리, 실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야 하고, 또 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있는 가능성이 높으므로 이러한 작업을 가상에서, 즉 메모리상에서 먼저 수행해서 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것
2.2.5 정리
가상 DOM과 리액트의 핵심은 브라우저의 DOM을 더욱 빠르게 그리고 반영하는 것이 아니라 바로 값으로 UI를 표현하는 것
화면에 표시되는 UI를 자바스크립트의 문자열, 배열 등과 마찬가지로 값으로 관리하고 이러한 흐름을 효율적으로 관리하기 위한 메커니즘이 바로 리액트의 핵심
개발자가 직접 DOM을 수동으로 하나하나 변경해야 한다면 어떤 값이 바뀌었는지, 또 그 값에 따라 어떠한 값이 변경됐고 이와 관련된 것들이 무엇이었는지 파악하기가 매우 어려울 것
이러한 어려움을 리액트 내부의 파이버와 재조정자가 내부적인 알고리즘을 통해 관리해 줌으로써 대규모 웹 애플리케이션을 효율적으로 유지보수하고 관리할 수 있게 된 것
2.3 클래스형 컴포넌트와 함수형 컴포넌트
2.3.1 클래스형 컴포넌트
클래스형 컴포넌트의 한계
데이터의 흐름을 추적하기 어려움
애플리케이션 내부 로직의 재사용이 어려움
기능이 많아질수록 컴포넌트의 크기가 커짐
클래스는 함수에 비해 상대적으로 어려움
2.3.3 함수형컴포넌트 vs. 클래스형 컴포넌트
render 내부에서 필요한 함수를 선언할때 this 바인딩을 조심할 필요 없음
state는 객체가 아닌 각각의 원시값으로 관리되어 훨씬 사용하기가 편함
렌더링하는 코드인 return에서도 굳이 this를 사용하지 않더라도 props와 state에 접근 가능
함수형 컴포넌트는 props를 받아 단순히 리액트 요소만 반환하는 함수인 반면, 클래스형 컴포넌트는 render 메서드가 있는 React.Component를 상속받아 구현하는 자바스크립트 클래스
함수형 컴포넌트와 렌더링된 값
함수형 컴포넌트는 렌더링이 일어날 때마다 그 순간의 값인 props와state를 기준으로 렌더링
클래스형 컴포넌트는 시간의 흐름에 따라 변화하는 this를 기준으로 렌더링
2.4 렌더링은 어떻게 일어나는가?
리액트의 렌더링은 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정을 의미
2.4.1 리액트의 렌더링이란?
리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 현재 자신들이 가지고 있는 props와 state의 값을 기반으로 어떻게 UI를 구성하고 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 일련의 과정을 의미
2.4.2 리액트의 렌더링이 일어나는 이유
리액트에서 렌더링이 발생하는 시나리오
1) 최초 렌더링: 사용자가 처음 애플리케이션에 진입하면 당연히 렌더링해야 할 결과물이 필요
2) 리렌더링: 리렌더링은 처음 애플리케이션에 진입했을 때 최초 렌더링이 발생한 이후로 발생하는 모든 렌더링을 의미
함수형 컴포넌트의 useState()의 두 번째 배열 요소인 setter가 실행되는 경우
함수형 컴포넌트의 useReducer()의 두 번째 배열 요소인 dispatch가 실행되는 경우
컴포넌트의 key props가 변경되는 경우
부모 컴포넌트가 리렌더링된다면 자식 컴포넌트도 무조건 리렌더링
2.4.3 리액트의 렌더링 프로세스
일반적으로 렌더링 결과물은JSX 문법으로 구성
JSX 문법에서 자바스크립트로 컴파일되면서 React.createElement()를 호출하는 구문으로 변환
createElement()는 브라우저의 UI 구조를 설명할 수 있는 일반적인 자바스크립트 객체를 반환
이런 과정을 거쳐 각 컴포넌트의 렌더링 결과물을 수집한 다음, 리액트의 새로운 트리인 가상 DOM과 비교해 실제 DOM에 반영하기 위한 모든 변경 사항을 차례차례 수집(재조정)
재조정 과정이 모두 끝나면 모든 변경 사항을 하나의 동기 시퀀스로 DOM에 적용해 변경된 결과물이 보이게됨
2.4.4 렌더와 커밋
리액트의 렌더링은 렌더 단계와 커밋 단계라는 총 두 단계로 분리되어 실행
렌더 단계(Render Phase)는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업
커밋 단계(Commit Phase)는 렌더 단계의 변경 사항을 실제 DOM에 적용해 사용자에게 보여주는 과정
이 단계가 끝나야 비로소 브라우저의 렌더링이 발생
이렇게 만들어진 모든 DOM 노드 및 인스턴스를 가리키도록 리액트 내부의 참조를 업데이트
그 다음 useLayoutEffect 훅 호출
2.5 컴포넌트와 함수의 무거운 연산을 기억해 두는 메모이제이션
2.5.1 주장 1: 섣부른 최적화는 독이다, 꼭 필요한 곳에만 메모이제이션을 추가하자
대부분의 가벼운 작업 자체는 메모이제이션해서 자바스크립트 메모리 어딘가에 두었다가 그것을 다시 꺼내오는 것보다는 매번 이 작업을 수행해 결과를 반환하는 것이 더 빠를 수 있음
메모제이션 비용 : 값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 작업, 그리고 이전에 결과물을 저장해 두었다가 다시 꺼내와야 한다는 두 가지 비용
2.5.2 주장 2: 렌더링 과정의 비용은 비싸다, 모조리 메모이제이션해 버리자
memo를 일단 그냥 다 적용하는 방법
렌더링 비용이 저렴하거나 사실 별로 렌더링이 안 되는 컴포넌트에 memo를 썼을 때 역으로 지불해야 하는 비용?
잘못된 memo로 지불해야 하는 비용은 바로 props에 대한 얕은 비교가 발생하면서 지불해야 하는 비용
반면 memo를 하지 않았을 때 발생할 수 있는 문제
렌더링을 함으로써 발생하는 비용
컴포넌트 내부의 복잡한 로직의 재실행
그리고 위 두 가지 모두가 모든 자식 컴포넌트에서 반복해서 일어남
리액트가 구 트리와 신규 트리를비교
비록 섣부른 초기화라 할지라도 했을 때 누릴 수 있는 이점, 그리고 이를 실수로 빠트렸을 때 치러야 할 위험 비 용이 더 크기 때문에 최적화에 대한 확신이 없다면 가능한 한 모든 곳에 메모이제이션을 활용한 최적화를 하는 것이 좋음