1. 특징
fragment
템플릿이 fragment와 string 타입을 지원하여, ReactElement 배열과 문자열에 대응합니다.
v16.2.0에서는 JSX의 fragment 지원인 <></>도 제공합니다.
error boundary
컴포넌트 수준의 오류 처리로, 하위 컴포넌트 트리 내부의 예외 포착을 지원하며 UI 계층의 최후 수단(fail-safe) 역할을 합니다.
portal
컴포넌트 트리와 DOM 트리 구조가 일치하지 않아도 되도록 허용하며, hovercards나 tooltips 등의 시나리오에 사용됩니다.
예를 들어 tooltip은 DOM 구조상 target과 tip이 보통 형제 관계(레이아웃 필요성)이지만, 논리적으로는 tip이 target에 속하는 부모-자식 관계입니다. Portals 기능은 이러한 시나리오를 처리하는 데 사용됩니다.
특이하게도 이벤트 버블링이 처리되어, portals 컴포넌트의 부모 컴포넌트는 여전히 버블링 알림을 받을 수 있습니다. (React 16 이전에도 DOM 이벤트 버블링 차이를 메우기 위한 이벤트 시스템이 내장되어 있었는데, 여기서는 우회 버블링 예시을 지원합니다.)
support for custom DOM attributes
이전에는 HTML/SVG 속성 이름 화이트리스트가 내장되어 있어 사용자 정의 속성이 차단되고 무시되었으나, React 16에서 이 제한이 사라졌습니다.
이 제한을 제거한 데에는 두 가지 이유가 있습니다. 첫째, 내장된 속성 필터링 레이어가 표준이 아닌(예: 제안 단계의) 새로운 속성이나 다른 라이브러리/프레임워크(예: Angular, Polymer)에 친숙하지 않기 때문입니다. 둘째, 번들에 상당한 크기의 속성 화이트리스트를 포함해야 하므로 유지보수가 꽤 번거롭기 때문입니다.
improved server-side rendering
React 15보다 3배 더 빠르다고 알려져 있으며(벤치마크 기준, 특정 비즈니스 시나리오에서는 1.3배), 다음과 같은 개선이 이루어졌습니다:
-
스트림(stream) 지원
-
빌드 시 불필요한
process.env접근을 제거했습니다. (Node 환경에서 이 변수에 접근하는 것은 시간이 많이 소요됩니다.) -
클라이언트에서 더 이상 체크섬(checksum)을 계산하지 않고 기존 DOM을 최대한 재사용합니다. (DOM 노드 재사용을 철저히 실천하는 inferno와 유사하지만, inferno의 재사용은 몇 가지 문제에 부딪힌 듯하며 현재는 핵심 기능보다는 선택 사항으로 제공됩니다.)
주의: React 16에서도 일부 DOM 노드 재사용 문제가 존재하는 것으로 보입니다:
However, it’s dangerous to have missing nodes on the server render as this might cause sibling nodes to be created with incorrect attributes.
P.S. 구체적으로 어떤 위험한 시나리오에 주의해야 하는지에 대해서는 공식 블로그에서 나중에 다룰 수 있으며, 현재는 명확히 설명되지 않았습니다.
reduced file size
React 번들 경량화(리팩토링, 플랫 번들링 전략 도입 및 Rollup으로 전환)를 통해 크기가 30% 줄어들었습니다.
2. SSR
가장 큰 변화는 SSR일 것입니다. 이번에는 제대로 구현되었습니다. (이전의 SSR은 덤으로 끼워준 것 같았죠.)
1. 새로운 API
서버 측에 renderToString과 renderToStaticMarkup에 각각 대응하는 renderToNodeStream 및 renderToStaticNodeStream이 추가되었습니다. 클라이언트 측에는 hydrate가 추가되었습니다.
2. 느슨한 일관성 검사
클라이언트 측 검사가 이전처럼 엄격하지 않습니다.
-
React 15에서는 클라이언트가 받은 SSR 결과에 대해 문자 단위의 일관성 검사를 수행하여 조금이라도 맞지 않으면 클라이언트에서 새로 생성하여 전체를 교체했습니다.
-
React 16은 속성 순서가 달라도 허용하며, 일치하지 않는 속성을 자동으로 복구하지 않습니다. 또한 태그 구조가 일치하지 않을 경우 전체를 교체하는 대신 서브 트리 수준에서 수정을 진행합니다.
또한 서버 HTML 구조에서 체크섬(data-react-checksum)과 ID(data-reactid)를 제거하여 응답 본문 크기가 상당히 줄어들었습니다:
<!-- react 15 -->
<div data-reactroot="" data-reactid="1"
data-react-checksum="122239856">
<!-- react-text: 2 -->This is some <!-- /react-text -->
<span data-reactid="3">server-generated</span>
<!-- react-text: 4--> <!-- /react-text -->
<span data-reactid="5">HTML.</span>
</div>
<!-- react 16 -->
<div data-reactroot="">
This is some <span>server-generated</span> <span>HTML.</span>
</div>
3. 성능 최적화
기본적으로 불필요한 process.env.NODE_ENV 접근을 제거하여 수동으로 컴파일할 필요가 없습니다.
SSR이 더 이상 일회성 가상 DOM(Virtual DOM)을 생성하지 않아 전체적으로 훨씬 빨라졌습니다.
스트림 지원으로 인한 성능 이점은 다음과 같습니다:
-
서버에서 생성되는 대로 즉시 전송하므로 SSR이 완료될 때까지 기다릴 필요가 없어 TTFB(the time to first byte)가 빨라집니다.
-
클라이언트에서 받는 대로 즉시 렌더링을 시작하므로 전체 응답이 올 때까지 기다릴 필요가 없습니다. 파싱, 렌더링, 외부 리소스 로드 시점이 모두 앞당겨졌습니다.
4. Error Boundary와 Portal 미지원
React 16 SSR은 Error Boundary와 Portal을 지원하지 않습니다.
서버 측에서 하위 컴포넌트 렌더링 중 오류가 발생해도 Error Boundary가 이를 막아주지 못합니다. 스트림 성능 이점을 위해 Error Boundary를 희생했습니다:
This is intentional / a known limitation. With streaming rendering it's impossible to "call back" markup that has already been sent, and we opted to keep renderToString and renderToNodeStream's output identical.
renderToNodeStream과 renderToStaticNodeStream뿐만 아니라 renderToString 역시 Error Boundary를 지원하지 않습니다. 위에서 언급했듯이 출력 결과를 동일하게 유지하기 위해 두 가지 메커니즘을 별도로 관리하지 않았습니다.
P.S. SSR Error Boundary에 대한 자세한 정보는 componentDidCatch doesn't work in React 16's renderToString을 확인하세요.
Portal 기능은 '리플로우(reflow)'를 발생시킬 수 있으며, 이는 Error Boundary와 같은 원리로 스트림 메커니즘에서는 지원이 불가능합니다. (이미 전송된 스트림에 Portal 내용을 삽입하는 것은 당연히 불가능하기 때문입니다.)
3. Fiber
완전히 새로운 핵심 아키텍처로, 2년에 걸쳐 컴포넌트 렌더링 메커니즘을 완전히 재작성했습니다. 가장 핵심적인 특징은 비동기 렌더링(async rendering)이며, 스케줄링 가능한 렌더링을 구현하여 마운트(mount) 과정이 시작되면 중단할 수 없었던 문제를 완전히 해결했습니다.
리팩토링 과정
이렇게 방대한 규모의 뼈대를 깎는 리팩토링 과정은 매우 흥미롭습니다. 간단히 요약하자면:
-
새로운 브랜치를 만들지 않고
useFiber라는 피처 플래그(feature flag)를 통해 전환했습니다. 이는 일상적인 유지보수와 충돌 처리를 단순화하기 위함이라고 합니다. -
먼저 골격(skeleton)을 만들고 일부 API를 지원한 뒤, 모든 테스트 케이스를 점진적으로 통과시켰습니다. (TDD, 즉 테스트 주도 개발 방식을 통해 최종적으로 2,000개의 케이스를 확보했습니다.)
-
엔지니어링 보조 수단들. 진척도 추적, 단위 테스트 결과 추적(특정 커밋이 무엇을 고쳤고 무엇을 망가뜨렸는지 쉽게 파악하기 위해
tests-failing.txt,tests-passing.txt를 Git으로 관리하는 아주 단순한 방법을 사용함), 지속적인 프로덕션 환경 검증(초기부터 테스트 통과 시까지 계속해서 '실전' 검증을 수행하는 소위 dogfooding 방식이며, 이는 일종의 가시적인 신념이라고 볼 수 있습니다.) -
적절한 비즈니스 서비스를 테스트 베드(testbed)로 삼았습니다. 어느 정도 안정된 후 실제 서비스를 통해 프로덕션 준비 완료를 증명했으며, A/B 테스트 데이터를 통해 2억 명의 사용자에게 검증받은 후 전면 도입했습니다. 동시에 사내 시스템도 모두 전환하여 검증 시나리오를 확대했고, 마지막으로 React Native 앱에도 점진적 배포(canary)를 실시했습니다.
-
새로운 메커니즘을 바로 적용하지는 않았습니다. 현재는 여전히 동기 방식으로 실행되지만(Fiber는 비동기를 지원함), 원활한 전환을 위해 몇 달 더 준비한 후 적용할 예정입니다.
P.S. 구체적인 리팩토링 과정은 React 16: A look inside an API-compatible rewrite of our frontend UI library를 참고하세요.
따라서 현재는 비동기 렌더링을 지원하지 않습니다:
This initial React 16.0 release is mostly focused on compatibility with existing apps. It does not enable asynchronous rendering yet. We will introduce an opt-in to the async mode later during React 16.x. We don't expect React 16.0 to make your apps significantly faster or slower, but we'd love to know if you see improvements or regressions.
장점
-
새로운 기능
컴포넌트 수준의 오류 처리,
render에서 여러 컴포넌트를 반환하는 등 이전에는 구현하기 어려웠던 기능들을 리팩토링 덕분에 만들 수 있게 되었습니다. -
사용자 경험(UX) 상의 이점
Fiber가 반드시 더 빠른 것은 아니지만, 훨씬 부드러워집니다. (렌더링 작업을 쪼개고 균형 있게 스케줄링하여 메인 스레드를 장시간 점유하는 것을 방지합니다.) 또한 작업 우선순위 제어를 통해 애니메이션 등을 우선적으로 실행할 수 있습니다.
차이는 꽤 명확하며, React Stack vs Fiber에서 확인하세요.
아직 댓글이 없습니다