18. 전문가를 위한 리액트
chap1 리액트의 탄생
-
우리가 겪는 문제 중, 리액트가 해결하는 것은 매우 일부
- 상태를 개별 플래그(isPending, hasFailed 등)에 저장할지, 아니면 단일 상태 변수(state등)에 저장할지 여부에 대해서는 리액트가 답을 내려주지 않음.
-
하지만 규모 문제만큼은 리액트가 해결해줌.
- 리액트는 상호작용이 필요한 버튼을 아주 많이 만들고, 매우 간결하고 효율적인 방식으로 이벤트에 반응하여 사용자 인터페이스를 업데이트함.
- 또, 이런 작업을 테스트하고 재연하는 것도 가능하며 선언적이고 성능이 뛰어나며, 예측하고 신뢰할 수 있는 방식으로 수행함.
-
또한, 리액트는 사용자 인터페이스 상태를 완전히 제어하고, 그 상태를 기반으로 렌더링함.
-
즉, 리액트는 오류 확률을 최대한 줄이고 안전하고 안정적으로 수행할 상당한 수준의 추상화를 해주는 격.
-
애플리케이션 상태를 추적할 수 있는 일종의 상태 조정 메커니즘이 없으면, 업데이트 시점을 예측하기 매우 어려워짐.
- 오류가 쉽게 생김
- 예측이 불가능함. (ex. 실수로 id 가 동일한 엘리먼트가 여러개 있다면?)
- 즉, 다뤄야할 요소가 자바스크립트와 HTML 양쪽에 존재한다면 어느쪽도 신뢰하기 어려움.
- 특정 엘리먼트의 존재 여부에 의존하면, 계속 업데이트해야하는 사용자 인터페이스 동작의 안정성을 보장하지 못함.
- 이런 경우, 우리가 작성하는 애플리케이션은 부작용(side effect)가 많아지고, 사용자 환경에서 안정성을 보장하기 어려움.
- 따라서 리액트는 함수형 프로그래밍에서 영감을 얻어 부작용을 의도적으로 드러내고, 격리하는 방식으로 이 문제를 해결함
- 비효율적임
- 리액트는 순차적으로 DOM 업데이트를 하지 않고, 일괄처리하여 한꺼번에 DOM에 적용함.
리액트 이전의 기술들
-
jQuery
- 이는 부작용이 많음. 왜? -> 코드 어느 곳에서든 페이지구조를 직접적으로 혹은 전역적으로 수정할 수 있음.
- 외부에서 불러오는 모듈은 물론이고, 원격에서 불러들인 코드에서도 가능함.
- 페이지 일부를 변경하면 예상하지 못한 방식으로 다른 부분에도 영향을 미쳐, 상호작용이 복잡해지고 동작을 파악하기가 난해해짐.
- 또한, jQuery에서 사용하는 패턴은, 코드 주변, 즉 코드가 인접한 애플리케이션 상태가 지속적으로 변하기 때문에 이해하고 테스트하기도 어려움.
- 또한, 브라우저 환경에도 크게 의존함.
-
Backbone
- 모델과 뷰를 생성하는 방법을 제공함.
- 전통적인 MVC 패턴을 해석함.
- MVC는 하지만 부족한점이 많음.
- 복잡한 상호작용 및 상태 관리
- 앱 규모가 커지면서 늘어난 컨트롤러 때문에, 상태 변경과 UI의 다양한 부분에 미치는 영향을 관리하기 어려워짐.
- MVC 구성요소 간의 경계가 명확하지 않아서 다른 컨트롤러와의 충돌을 일으키기도 함.
- 양방향 데이터 바인딩
- 뷰가 모델과 동기화되지 않거나, 모델이 뷰와 동기화되지 않기도 하는 부작용이 발생 할수 있음.
- 데이터 소유권 문제가 대충 처리되거나, 관심사가 명확하지 않은 경우도 있음.
- 즉, MVC 의 가장 큰 강점인 관심사분리는 강제성이 없을 때 약점이 되기도함.
- 리액트는 단방향 데이터 바인딩을 사용함. (이를 통햇거 데이터 흐름의 우선순위를 정하고 강제함)
- 강한 결합
- Model, View, Controller 가 강결합 되어있 다른 구성요소에 영향을 주지 않고 독립적으로 하나만 변경하거나 리팩터링하기 어려워짐.
- 또한 backbon 은 이벤트 중심 아키텍쳐임.
- 즉, 모델 데이터를 업데이트하면 전체 애플리케이션에서 수많은 이벤트가 발생 할 수 있음. 따라서, 이벤트는 예측하기 어려워서 유지보수에 어려움을 줌.
- 또한, 뷰 조합성도 부족함.
- 뷰를 쉽게 중첩 할수있는 기능이 내장되지 않아 복잡한 사용자 인터페이스를 구성하기 어려움.
-
Knockout
- 옵저버블과 바인딩을 사용하여, 상태가 변경될 때 마다 옵저버블과 바인딩으로 의존성을 추적함.
- 반응형 자바스크립트 라이브러리.
- 반응형은, 관찰 가능한 방식으로 상태 변화에 따라 값을 업데이트하는 것.
- 현대적으로 구현한건 signal
- MVVM 디자인패턴 사용.
- 옵저버블을 명시적으로 구독하고, 변경에 따라 사용자 인터페이스를 업데이트하려면 많은 작업이 필요함. 즉, 보일러플레이트가 많이 필요함.
- 또한, 뷰모델이 거대해지고 복잡해지면서 리팩터링/최적화가 불확실해짐.
-
앵귤러
- 양방향 데이터바인딩
- 모델 (기반 데이터)이 변경되면 뷰(UI)도 변경사항을 반영하도록 자동 업데이트 됨. vice versa
- jQuery에서 개발자가 DOM을 수동으로 조작하여, 데이터의 변경사항을 반영하고, 사용자 입력을 캡쳐하여 데이터 업데이트하는 것과 반대.
- 모듈식 아키텍쳐
- 애플리케이션의 구성 요소를 논리적으로 분리할 수 있도록 모듈식 아키텍쳐를 도입함.
- 모듈은 기능을 캡슐화하여 독립적으로 개발,테스트, 유지 관리가 가능함.
- 의존성 주입 (DI)
- 객체가 의존성을 직접 만드는대신, 의존성을 전달받는 디자인 패턴.
- 모듈과 구성요소를 만들고 관리하는 방식에 큰 영향을 미치고 모듈성과 재사용성을 높임
- 양방향 데이터바인딩
리액트 등장
- 리액트는 컴포넌트 기반 아키텍쳐.
- 단방향 데이터 흐름 패턴을 도입함.
- 직접적인 DOM 조작 최소화.
- 선언적 코드
- 리액트는 우리가 보고자하는 것을 코드로 표현하는 방법을 제공하고, 실제로 어떻게 할지는 리액트가 알아서 함.
- 컴포넌트 모델
- 불변성
- 각각의 상태 업데이트는 새로운 독립된 스냅샷과 메모리참조로 취급됨.
- 불변하는 값을 통하여 접근하는 상태 관리 방식은 리액트의 핵심가치이며, 강력하고 효율적임.
- 리액트는 불변성을 강제하며, UI 컴포넌트가 특정 시점의 특정 상태를 반영하도록 보장함.
- 상태가 변겨되면 원래 있던 상태를 직접 변경하는 대신, 새로운 상태를 표현하는 새 객체를 반환함.
- 따라서 모든 상태 전환은 독립적이고 서로 간섭하지 않음.
- 따라서, 가변상태를 공유할 때와 다르게 탐지하기 어려운 버그의 발생 여지가 적어짐.
플럭스 아키텍쳐
- 단일 정보 출처
- 플럭스에서는 애플리케이션의 single source of truth 가 스토어에 저장됨.
- 이러한 중앙집중식 상태관리는 애플리케이션의 동작을 더 예측하기 쉽고, 이해하기 쉽게 만듦.
- 테스트 가능성
- 시스템의 여러 부분 (액션, 디스패쳐, 스토어, 뷰) 간에 관심사를 분리하면 각 부분을 독립적으롣 ㅏㄴ위테스트 가능함.
chap2 JSX
- JSX 의 X는 자바스크립트의 확장구문(eXtension)
- 자바스크립트 XML 이라고도 함.
- 별도의 언어가 아닌, 컴파일러 or 트랜스파일러에 의해 일반 자바스크립트 코드로 변환되는 확장구문.
내부 동작
-
코드를 컴파일하는 과정은 어휘 분석, 구문 분석, 의미 분석, 최적화, 코드 생성 등 여러 단계를 통과함.
-
컴파일러는 3단계 과정을 거침. 토큰화 (tokenization), 구문 분석(parsing), 코드 생성(code generation)
-
토큰화
- 문자열을 의미있는 토큰으로 분해하는 것.
- 토크나이저가 상태를 가지고 있고, 각 토큰이 자신의 부모나 자식에 관한 상태를 포함하고 있을때는 토크나이저를 렉서라고 부름.
- 한마디로, 렉싱(lexing)은 상태를 가지는 토큰화.
- 렉서 규칙으로 정규표현식을 사용하여 프로그래밍 언어를 나타내는 텍스트 문자열에서 변수 이름, 객체 . 키 및 값같은 주요 토큰을 감지함.
- 그런다음 렉서는 구현에 따라 이러한 키워드를 열거 가능한 값으로 표현함. 가량 const 는 0으로, let 은 1로, function은 2로.
-
구문분석
- 토큰을 가져와 구문분석 트리로 변환
- 구문 트리는 코드의 구조를 나타내는 자료구조.
- 구문 분석기로 인해 문자열은 JSON 객체가 됨.
-
코드 생성
- 컴파일러가 AST (Abstract Syntax Tree)에서 기계어를 생성하는 과정.
-
컴파일러의 종류 중 JIT 과 인터프리터
- JIT(Just In Time) 컴파일러
- 코드를 미리 변환하지 않고, 실행할 때 기계어로 변환한다.
- JIT 컴파일러는 자바 가상머신 같은 가상 머신에서 일반적으로 사용되고, 기존 인터프리터보다 성능이 좋음.
- 인터프리터
- 컴파일하지 않고 소스코드를 직접실행함.
- 인터프리터는 일반적으로 컴파일러보다 속도가 느리지만 유연상, 사용편의성이 우수함
- JIT(Just In Time) 컴파일러
-
실행할 때 컴파일하면 뭐가 좋음?
- 엔진은 변수 종류와 자주 실행되는 코드 경로(hot path)같은 실시간 정보를 기반으로 최적화를 수행 가능함.
- 런타임은 일반적으로 엔진과 연동하여 특정 환경에 맞는 콘텍스트 헬퍼와 같은 기능을 더 많이 제공함.
- context helper 란?
- v8의 hidden class, inline caching, gc 등
- context helper 란?
- 가장 인기있는 런타임은 구글 크롬, 서버는 V8엔진을 사용하는 Node.js 런타임.
-
그런데 이런게 JSX 랑 무슨상관?
- JSX 를 어떻게 동작하게 만듦? -> 자바스크립트 구문을 확장하려면 새로운 구문을 이해하는 다른 엔진이 필요하거나, 엔진보다 앞서 새로운 구문을 처리해줘야함.
- 허나 새로운 엔진을 만드는 것은, 다양한 환경에서 돌아가야하는 JS 특성상 불가능함.
- 즉, 엔진 앞단에서 새로운 언어를 분석하는 렉서와 구문분석기가 필요함.
- 즉, 구문분석트리를 사용하여 자바스크립트 엔진이 이해가능한 바닐라 JS 를 생성함.
- 이것이 바로 자바스크립트 생태계에서 Babel이 하는 일임. (혹은 typescript, swc)
- 이는 '전처리기' 즉, '트랜스파일링'을 함.
- 따라서 JSX 는 브라우저에서 바로 실행 불가능하고, 구문트리르로 컴파일하는 빌드단계가 필요함.
- 이 코드는 이러한 과정을 거쳐, 바닐라 JS 로 변환되어 최종 배포용 번들에 포함됨.
- 이러한 과정을 트랜스파일링이라고함.
-
JSX 표현식
- 엘리먼트 트리 내부에서 코드를 실행함.
- 변수, 함수호출, 계산결과 등을 쉽게 렌더링 가능함.
- 또한, 이를 표현식을 통해 "어떻게"가 아닌, "무엇을"표현하는 방식을 차용함.
chap 3. 가상 DOM
- HTML 문서를 자바스크립트 객체로 모델링한 것.
- 실제 DOM 은 노드 객체로 구성되고, 가상 DOM 은 설명 역할을 하는 평범한 JS 객체로 구성됨.
- 가상 DOM 은 DOM 처럼 변동성이 큰환경에서 상태 관리에 대한 걱정을 덜 수 있다는 점에서 리액트의 가상 DOM 이 제공하는 가치는 의심할 여지가 없음
- 실제 DOM은 사용자 상호작용, 네트워크 요청, 클라이언트 측 스크립트, 언제든 변경될 수 있는 기타 이벤트 등 수많은 요소의 영향을 받기에 변동성이 큼.
- 리액트의 가상 DOM은 이러한 변동성이 큰 환경에서 개발자를 보호함.
실제 DOM 의 문제점
- 성능
- 엘리먼트 속성 업데이트 등으로 DOM 변경시마다 브라우저는 reflow & repaint
- 이는, 크고 복잡한 웹페이지를 처리할 때 큰 부담이 된다.
- CSS 선택자 최적화, 이벤트 위임 사용 , read/write DOM 작업 일괄 처리, CSS 애니메이션 사용 등 이러한 문제를 완화하는 기술이 있으나, 복잡하고 구현이 어려움.
- 따라서 가상 DOM 은 이러한 실제 DOM 의 복잡성을 추상화하고, UI 를 더 가볍게 표현함으로써 보다 효율적이고 성능이 뛰어나게 UI를 제작할 수 있음.
- 브라우저 간 호환성
- 브라우저 마다, 문서 모델링 방식이 다랄서 웹 애플리케이션의 일관성이 보장되지 않고, 이로 인해 버그에 취약함.
- 리액트의 합성 이벤트 시스템 (synthetic event system)이 해결하고자 한문제가 바로 그것.
- SyntheticEvent 는 브라우저의 기본 이벤트를 둘러싼 wrapper 객체로, 여러 브라우저에서 일관성을 보장하기 위해 설계됨. 이 객체는 아래와 같은 방법으로 일관성을 보장함.
- 통합 인터페이스
- 브라우저에서 이벤트 속성에 접근하는 방법이 제각각. event.target 인 것도 있고, event.srcElement 인 것도 있음.
- Synthetic Event 는 이러한 차이를 추상화하여 이벤트와 상호작용하는 일관된 방법을 제공하여, 개발자가 브라우저 호환성을 신경쓰지 않아도 되게 함.
- 이벤트 위임
- 리액트는 이벤트 리스너를 개별 엘리먼트에 직접 추가하지 않고, 루트에서 모든 이벤트를 받음.
- 요 방식은, 구형 브라우저에서 특정 엘리먼트에서 일부 이벤트를 사용할 수 없는 문제를 방지함.
- 다양한 기능 개선
- 네이티브 브라우저 이벤트의 비일관성은 다양한 입력 엘리먼트에서 특정 이벤트를 처리하는 방식에서 드러남.
- 리액트의 synthetic Event 시스템은, 이러한 입력 엘리먼트 전체에서 onChange 이벤트의 동작을 정규화하여, 일관된 경험을 보장한다.
- 네이티브 이벤트에 접근
- 웹 브라우저의 네이티브 이벤트가 필요한 경우, event.nativeEvent 를 통하여 접근도 가능함.따라서 추상화의 이점을 그대로 취하되, 필요에 따라 유연하게 사용가능함.
- 통합 인터페이스
문서 조각
- DOM 이 업데이트 될 때 마다 reflow&repaint 가 발새앟면 애플리케이션 속도가 느려질 수 있음. 이 때 문서조각의 역할이 중요함.
- 문서 조각은 DOM 노드를 저장하는 가벼운 컨테이너임.
- 기본 DOM 에 영향을 주지 않고, 여러가지 업데이트를 수행할 수 있는 임시 저장소처럼 동작함.
- 업데이트 작업이 완료되면 문서 조각을 DOM에 추가하는 방식으로 reflow & repaint 를 한번씩만 발생시킴.
- 즉 아래와 같은 성능상 이점이 있음.
- 일괄 업데이트
- 문서의 실제 DOM을 여러번 개별적으로 업데이트하는 대신, 문서 조각 내의 모든 변경사항을 일괄적으로 처리함.
- 다시말해, 문서 조각 내에서 얼마나 많은 엘리먼트나 업데이트를 수행했든, reflow & repaint 는 한번씩만 수행됨.
- 메모리 효율성
- 문서 조각에 추가된 노드는 문서의 실제 DOM에서 제거됨. 이는 문서에서 큰 영역을 재정렬할 때 메모리 사용량을 최적화하는데 일조함.
- 중복 렌더링 방지
- 문서 조각은 활성화된 문서 DOM 트리에 속하지 않으므로, 문서 조각을 변경해도 실제 문서에는 영향을 주지 않으며, 실제 DOM에 추가될 때 까지 스타일과 스크립트가 적용되지 않음.
- 리액트의 가상 DOM은 문서 조각 개념을 더 나은 방식으로 구현한 것으로 볼 수 있음.
- 문서 조각은 문서의 실제 DOM을 업데이트하기 전에 변경사항을 그룹화하여 최적화하지만, 리액트의 가상 DOM 애플리케이션 UI 전반에 걸쳐 차이점을 파악하고, 일괄적으로 업데이트를 해 렌더링 효율성을 극대화함.
- 또한, 문서조각과 관련된 복잡한 작업을 개발자가 신경쓰지 않도록 추상화해둠.
- 덕분에 개발자는 제품에 더 집중할 수 있게 됨.
가상 DOM 작동 방식
- 가상 DOM 과 실제 DOM 비교
- 리액트의 가상 DOM 은 트리 같은 엘리먼트 구조를 표현함
- 리액트 컴포넌트가 렌더링되면, 리액트는 새 가상 DOM트리를 생성하고, 이전 가상 DOM트리와 비교한다음, 이전 트리를 새 트리와 일치하도록 업데이트하는데 필요한 최소 변경횟수를 계산함.
- 이를 재조정 프로세스라고 함.
- 효율적인 업데이트
- 리액트 컴포넌트의 상태나, 프롭이 변경되면 리액트는 업데이트된 사용자 인터페이스를 표현하는 새로운 리액트 엘리먼트 트리를 생성함.
- 그런 다음, 비교 알고리즘을 사용하여 새 트리를 이전트리와 비교하여 실제 DOM 업데이트에 필요한 최소한의 변경범위를 결정함. -> 이 과정을 diffing 이라고함.
chap4. 재조정
- 가상 DOM 은 우리가 원하는 UI 상태의 청사진.
- 리액트는 이 청사진을 갖고, 재조정이라는 프로세스를 통해 주어진 호스트환경(브라우저 or 네이티브)에서 현실로 만드는 역할을 함.
일괄 처리
- 여러 가상 DOM 업데이트를 모아 한번의 DOM 업데이트로 결합한 후, 실제 DOM에 반영함.
- 이렇게하면 실제 DOM 업데이트 횟수가 줄어, 성능 개선이 됨.
스택 재조정자 (16미만 버전에서 사용)
- LIFO(Last in, First out)
- 즉, 스택에 마지막으로 추가된 원소가 가장 먼저 제거됨.
- 스택 기반 알고리즘을 사용하여, 새 가상 트리를 이전 가상 트리와 비교하고, 그에 따라 DOM을 업데이트함.
- 스택 재조정자는 간단한 경우에는 잘 작동했으나, 애플리케이션 규모가 커지고 복잡해지면서 여러가지 문제가 발생함.
- 스택 재조정자는 작업을 일시 중지하거나 연기하지 않고, 순차적으로 변경사항을 렌더링함.
- 계산 비용이 비싼 컴포넌트가 렌더링을 막아버리면, 사용자 입력이 눈에 띄게 버벅거리고 사용 경험이 안좋아짐.
- 만약, 중요도는 낮지만 계산 비용이 큰 컴포넌트의 렌더링보다 사용자 입력의 우서눈위를 높게 설정하여 입력 내용을 화면에 바로 반영하고, 계산 비용이 큰 컴포넌트를 뒤늦게 렌더링한다면 사용자 경험이 좋아질 것임.
- 즉, 렌더링 파이프라인에서 우선순위 조정에 대한 니즈가 발생함
- 또한, 스택 재조정자는 업데이트를 중단하거나 취소할 수 없음.
- 따라서 설령 업데이트 우선순위를 조정가능하다 해도, 우선순위가 높은 업데이트를 위해 덜 중요한 업데이트를 중단하는게 불가능한 것.
파이버 재조정자
-
파이버라는 데이터 구조를 사용함.
-
파이버는 3장에서 다룬 리액트 엘리먼트에서 생성되는데, 핵심적인 차이는 파이버는 상태를 저장하고, 수명이 긴 반면, 리액트 엘리먼트는 임시적이고 상태가 없다는 것.
-
"특정 시점에 존재하는 실제 컴포넌트 트리를 나타내는 리액트의 내부 데이터 구조" by. 마크 에릭슨
-
데이터 구조로서의 파이버
- 재조정자의 핵심 요소.
- 파이버 재조정자는 업데이트의 우선순위를 정하고, 이에 따라 동시 실행을 가능케 하여 리액트 애플리케이션의 성능과 응답성을 향상시킴.
- 파이버 데이터 구조는 리액트 애플리케이션에서 컴포넌트 인스턴스와 그 상태를 표현함.
- 이는 변경가능한 인스턴스로 설계되었으며, 조정과정에서 필요에 따라 업데이트되고, 재배치됨.
- 파이버 노드의 각 인스턴스에는...해당 컴포넌트의 프롭,상태,하위 컴포넌트 등이 포함됨.
- 뿐만 아니라, 컴포넌트 트리에서의 위치 정보, 파이버 재조정자가 업데이트 우선순위를 정하고 실행하는데 사용하는 메타데이터도 들어있음.
-
- 파이버 재조정은 더블버퍼링과 비슷함.
- 업데이트가 발생하면 현재 파이버 트리가 포크되어 주어진 사용자 인터페이스의 새로운 상태를 반영하도로 업데이트 됨.
- 이를 렌더링이라고 함.
- 그 후 현재 트리를 대체할 트리가 준비되고, 사용자가 기대하는 상태를 정확하게 반영하면 현재 파이버트리와 교체됨. (이를 제조정의 Commit Phase 이라고 함.)
- 파이버 재조정자를 사용하면 JSX 엘리먼트의 사용자 정의 트리에서 두 가지 트리가 만들어짐.
- 현재 파이버를 포함하는 트리
- 작업용 파이버를 포함하는 트리
-
파이버 재조정
-
Render Phase와 Commit Phase로 이루어짐
-
이 덕분에, DOM 커밋해서 사용자에게 보여주기 전에 언제든 폐기가능하게 됨. 즉, 렌더링 중단이 가능해짐.
-
렌더링 중단이 가능한듯이 보이는 것은 리액트 스케쥴러가 5밀리미초 마다 실행을 메인스레드로 돌려주기 때문.
- 렌더링 중간중간에 메인스레드에서 다른 기능이 관여할 여지를 둔다는 의미.
-
- 이는 현재 트리에서 상태 변경 이벤트가 발생하면 시작함.
- 리액트는 각 파이버를 재귀적, 단계적으로 순회하고 업데이트가 보류중이라는 신호 플래그를 설정해 대체 트리에 오프스크린 변경작업을 수행함.
-
- 렌더링 단계에서 가상 DOM 에 적용된 변경사항을 실제 DOM에 반영함.
- 새 가상 DOM 트리가 호스트환경에 커밋되고, 작업용 트리가 현재 트리로 바뀜.
-
효과 (Effect)
- 리액트 재조정 과전의 커밋단계에서는 여러 부작용이 특정 순서로 실행됨.
- 배치(placement) effect
- 새 컴포넌트가 DOM 에 추가될 때 발생함.
- 업데이트 effect
- 컴포넌트가 새로운 프롭이나 생태로 업데이트 될 때 발생함.
- 삭제 effect
- 컴포넌트가 dom 에서 제거될 때 발생함.
- 레이아웃 effect
- 브랑저의 페인트 가능 시점 전에 발생하며, 페이지 레이아웃을 업데이트하는 데 사용됨.
- 이러한 커밋 단계의 효과와 달리, 패시브(passive effect)는 브라우저 페인트 가능시점 후에 실행되도록 예약된 사용자 정의효과이며, useEffect 훅을 사용하여 관리가능함.
- 패시브효과는 API 에서 데이터 요청이나 분석 처럼 페이지의 초기 렌더링과 관련되지 않은 작업을 수행하는데 유용함.
-
Chap 5. 자주묻는 질문과 유용한 패턴
1. React.memo 를 사용한 메모화
-
메모화는 함수의 순수성이 필수조건임.
- 다시 말해, 함수가 주어진 입력에 대해 동일한 출력을 예측 가능하게 반환해야함.
-
React.memo 로 감싸진 함수는 프롭이 변경되지 않는 한 재조정 과정에서 다시 호출되지 않음.
-
React.memo는 프롭에 얕은 비교를 수행하여, 프롭의 변경 여부를 확인함.
- 문제는 스칼라타입이 아닌, 참조타입임.
- 참조타입은 알다싶이, 내용은 같아도 메모리 주소가 다름.
- 따라서, 참조타입을 비교할 때에는 값이 아닌 주소를 기준으로 비교함. (스칼라 타입은 값을 기준으로 비교함)
-
(주의 사항) 리액트에서 내장 컴포넌트 또는 호스트 컴포넌트(div, button, input 등)는 함수 프롭 등의 프롭을 사용자 정의 컴포넌트와 조금 다르게 취급함
- 함수 프롭 (onClick) 을 내장 컴포넌트에 전달하면, 리액트는 이를 실제 DOM 엘리먼트에 직접 전달함.
- 이렇나 함수에 래퍼 생성등의 추가 작업을 하지 않음.
- 특히 onClick 같은 이벤트 기반 프롭의 경우, 리액트는 이벤트 핸들러를 DOM 엘리먼트에 직접 추가하지 않고 이벤트 위임을 사용해 이벤트를 처리함.
- 내장 컴포넌트는 리렌더링된 상위 컴포넌트의 일부가 아니라면, 함수프롭의 변경에 의해서는 리렌더링되지 않음.
- 부모 컴포넌트가 리렌더링해서, 내장 컴포넌트에 새로운 함수를 프롭으로 전달하는 경우, 내장 컴포넌트는 프롭이 변경되었기 때문에 리렌더링됨.
- 하지만 이 때의 리렌더링은 매우 빠르기 때문에, 대체로 프로파일링 등을 통해 성능에 문제가 된다고 판명되지 않는한 최적화 될 필요 없음.
- 함수에 대한 가상 DOM은 비교하지 않음.
- 내장 컴포넌트에 대한 가상 DOM 비교는 함수 프롭의 동일성을 기반으로 함.
- 인라인함수를 전달하면 컴포넌트가 렌더링될 때 마다, 새로운 함수가 되지만, 리액트는 변경사항을 감지하기 위해 함수에 대해 깊은 비교를 수행하지는 않음.
- 단순히 새 함수는 DOM 엘리먼트에 설정된 기존 함수를 대체하게 되며, 덕분에 내장 컴포넌트에서 성능이 절약됨.
- 이벤트 풀링
- 리액트는 이벤트 핸들러에 이벤트 풀링을 사용하여, 메모리 부하를 줄임.
- 이벤트 핸들러에 전달되는 이벤트 객체는, 풀링된 합성 이벤트인데, 여러 이벤트 핸들러에 재사용됨으로써 가비지 컬렉션 부하를 줄임.
- 함수 프롭 (onClick) 을 내장 컴포넌트에 전달하면, 리액트는 이를 실제 DOM 엘리먼트에 직접 전달함.
-
즉, 내장컴포넌트의 경우 메모이제이션을 적용할 경우 성능 개선의 효과 없이, 부하만 추가됨으로써 리액트는 내장 메모화를 제공하지 않음.
-
그러면 useCallback 을 쓰면 좋은 사례는?
- 자주 리렌더링할 가능성이 있는 컴포넌트가 있고, 하위 컴포넌트에 콜백을 전달할 때, 그리고 하위 컴포넌트가 React.memo 또는 shouldComponentUpdate 로 최적화 된 경우 매우 유용함.
- 콜백을 메모화하면 부모 컴포넌트를 렌더링할 때, 자식 컴포넌트가 리렌더링 되지 않음.
의견..
리액트 19에서는 자동메모이제이션 기능이 추가됨으로써, 개발자가 이런것들을 조금 덜 신경써도 되어져서 좋은듯. 늘 리액트에서 성능 최적화 등을 고려하며 개발하는게 매우 어렵다고 생각함ㅎㅎ 자식 컴포넌트에 내려지는 콜백인가 아닌가에 따라서, 그리고 그 자식컴포넌트가 메모이제이션을 하고있는가 아닌가에 따라서 부모 컴포넌트에서 콜백에 메모이제이션 등을 추가하는 작업들이 매우 복잡하여 실수 또는 부정확한 최적화만 늘어난 다고 생각함. 이런 것들이 어느정도 리액트 자체에게 위임되어서 다행인듯.
2. Lazy loading
- 코드 분할을 통해 특정 페이지나 기능에 필요한 자바스크립트만 읽어들여서 성능을 최적화 할수 있음.
- 레이지로딩은, 페이지가 완전히 읽어 들여질 때 까지, 초기실행에 필수적이지 않은 자바스크립트의 로딩을 미루어 두는 것.
- 필요할 때만 코드를 읽어들이므로 페이지 로드시간과 데이터 사용량을 줄일 수 있음.
- 또한 서스펜스와 레이지로딩을 함께 사용할 수 있음.
- 서스펜스는 프라미스가 resolved 되기 전 까지, 폴백 컴포넌트를 표시하도록 함.
3. useState vs useReducer
- useState 는 단일 상태를 관리하는데 더욱 적합하고,
- useReducer 는 복잡한 상태를 관리하는데 더욱 적합함.
- fun facts > useState 는 내부적으로 useReducer 를 사용한다. 즉, useState 는 useReducer 의 상위 추상화라고 보면 됨~!
- useReducer 는 너무 장황하다~?
- 추상화 계층에서 한단계 내려갈 수록 코드가 장황해지는 것은 어쩔 수 없음.
- 그만큼 한단계 낮은 레벨의 추상화이므로, 장황해지는 대신, 제약사항이 느슨해져서 다양한 요구사항을 구현할 수 있는거아닐까~
- 그런데 왜 useReducer 를 사용해야할까?
- 상태 업데이트로직을 컴포넌트로부터 분리 가능함.
- 상태와 상태 변경방식은 항상 명시적으로 useReducer 와 함께 사용됨.
- useReducer 는 이벤트소스(Event Source) 모델임.
- 즉, 애플리케이션에서 발생하는 이벤트를 모델링하여 일종의 진단로그를 추적하는데 사용가능함
- 이 진단 로그는, 애플리케이션의 이벤트를 재상하여 버그를 재연하거나 타임트레블링을 할수 있게함.
- 또한, 실행취소, 재실행, 낙관적 업데이트와 같은 강력한 패턴도 사용가능~
4. 다양한 패턴들
... 그냥 패턴 사용의 나열이어서, 생략하고 흥미로웠던것만 가져옴.
훅을 사용하는 경우와, 고차컴포넌트를 사용하는 경우
- 이미 고차컴포넌트의 기능은 훅으로 대체되는데, 왜 굳이 고차컴포넌트를 써야하는 상황이 올까?
- 고차컴포넌트는 렌더링에 직접적인 영향을 줄 수 있고, 프롭을 직접 주입하거나 조작할 수도 있음.
- 즉, 훅은 단순히 비즈니스 로직만 주입가능한 도구라면, 고차컴포넌트는 비즈니스 로직과 더불어 프롭 주입/조작 및 렌더링 조작 등을 할 수 있는게 가장 큰 차이점이라고 봄.
Chap 6. 서버사이드 리액트
CSR 의 한계
- SEO
- 성능
- 느린 네트워크, 낮은 성능의 기기 에서 문제
- 콘텐츠를 렌더링하기 전에 자바스크립트를 다운로드 하고, 구문 분석과 실행까지 해야하므로, 콘텐츠 렌더링이 지연될 수 있음.
- 보안
- 애플리케이션의 모든 코드가 클라이언트의 브라우저로 다운로드 되어, 크로스 사이트요청 위조같은 공격에 취약함
서버렌더링의 장점
- 최초의 의미있는 페인트 (First Meaningful paint)가 빨라짐
- 서버가 초기 HTML 마크업을 렌더링해 클라로 전송하면, 바로 콘텐츠를 볼 수 있음.
- 웹앱의 접근성 개선
- 인터넷 연결속도가 느리거나, 저전력 기기를 사용하는 사용자들.
- SEO 개선
- 보안 향상
그런데, 서버에서 가져온 html 에는 이벤트 핸들러 등이 바인딩되어있지 않은 매마른 상태임. 즉 상호작용할수 있도록 물을 줘야함. 바로바로~ 하이드레이션
하이드레이션
- 서버에서 생성되어 클라로 전송된 HTML 에 이벤트 리스너와 여러 자바스크립트 기능을 바인딩하는 프로세스.
- 하이드레이션의 목적은, 브라우저가 서버 렌더링 애플리케이션을 읽어들인 후 여기에 상호작용을 추가하여 사용자에게 바르고 원활한 경험을 제공하는 것.
- 리액트 어플리케이션에서 하이드레이션은 클라가 서버에서 렌더링된 리액트 애플리케이션을 다운로드한 후에 발생함. 이후에는 아래의 단계로 감.
- 클라이언트 번들 로딩
- 브라우저는 정적 html 을 렌더링하는 동안, 애플리케이션 코드가 포함된 자바스크립트 번들을 다운로드하고 파싱함.
- 이벤트 리스너 추가
- 자바스크립트 번들이 로드 되면, 리액트는 이벤트 리스너 및 기타 동적 기능들을 dom 요소에 추가하여 정적 html 을 하이드레이션함
- 이 동작은 일반적으로 react-dom 패키지의 hydrateRoot 함수를 사용해 수행되며, 이 함수는 루트 리액트 컴포넌트와 DOM 컨테이너를 인수로 받음.
- 하이드레이션이 완료되면 정적 html 이 완전히 인터랙티브 리액트 애플리케이션이 됨.
- 클라이언트 번들 로딩
하이드레이션에 대한 비판
-
일부에서는 하이드레이션이 필요 이상으로 느리다고 비판하며, 재개 가능성(resumability)을 더 나은 대안으로 꼽기도 함.
-
하이드레이션은 서버에서 리액트 애플리케이션을 먼저 렌더링한 후, 렌더링된 출력을 클라에 전달함.
-
그런데 이 시점까지는 인터랙티브한 기능이 없음.
-
이후 브라우저는 클라 번들을 다운로드하고, 이벤트 리스너를 추가한후, 클라를 '리렌더링' 해야함.
-
많은 작업을 필요로 하며 때로 콘텐츠가 사용자에게 표시되는 시점과 사용자가 실제로 사이트를 사용할 수 있는 시점사이에 지연이 발생함.
-
재개 가능성은?
- 전체 애플리케이션이 서버에서 렌더링되어 브라우저로 스트리밍 됨.
- 초기 마크업과 함께 모든 인터랙티브 동작이 직렬화되어 클라로 전송됨.
- 이 시점에서 클라는 이미 인터랙티브해지는 방법에 대한 모든 정보를 가지므로, 서버가 중단한 부분부터 다시 시작할 수 있음.
- 즉, 하이드레이션 필요없이 서버가 준 내용을 역직렬화하여 그에 따라 반응가능.
- 그런데 구현의 복잡성이 큼
다양한 서버사이드 렌더링 api 들
- renderToString 은 서버에서 리액트 컴포넌트를 HTML 문자열로 렌더링
- 이 API 는 동기식으로 동자갛며 완전히 렌더링된 HTML 문자열을 반환함.
- renderToPipeableStream
- 대고뮤 리액트 애플리케이션을 Node.js 스트림에렌더링하는 보다 효율적인 방법
- 이 API 는 응답 객체로 파이프할 수 있는 스트림을 반환하며, HTML 이 렌더링되는 방식을 더 세밀하게 제어할 수 있음.
- 또한, 리액트의 동시성 기능, 즉 서버 사이드 렌더링 중 비동기 데이터 페치를 더 잘 처리해주고, Suspense 를 지원해줌.
- 스트림이기 때문에 네트워크를 통해 스트리밍할 수 있으며, HTML 청크를 클라에 비동기적으로 전송하여 네트워크 지연 없는 점진적인 데이터 전달이 가능함.
- renderToReadableStream
- 이는 브라우저 환경에서 읽을 수 있는 스트림으로 전송함.
- Node.js 스트림은 이벤트 기바이며 서버 측 작업에 적합한 반면, 브라우저 스트림은 최신 웹 표준에 따라 프라미스 기반이며 클라이언트 측 작업에 맞게 조정됨.