diff --git a/docs/RookieAND/dil/2024-03-08.md b/docs/RookieAND/dil/2024-03-08.md new file mode 100644 index 0000000..147bf38 --- /dev/null +++ b/docs/RookieAND/dil/2024-03-08.md @@ -0,0 +1,214 @@ +# ✒️ JSX + +### ✏️ JSX 란 무엇인가? + +> JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. + +- JSX 는 Meta 에서 독자적으로 개발한 JS 내에 HTML Markup 코드를 삽입할 수 있도록 돕는 문법 확장 기능이다. +- ECMAScript 나 V8 엔진의 표준이 아니기 때문에 JSX 코드를 그냥 실행하면 문법 오류가 나며, babel 과 같은 트랜스파일러를 통해 코드를 변환한 후 실행해야 한다. + +# ✒️ JSX 의 구조 + +JSX 는 `JSXElement`, `JSXAttributes`, `JSXChildren`, `JSXString` 의 4가지 요소로 구성된다. + +### ✏️ JSXElement + +- JSX 를 구성하는 가장 기본적인 요소이며, HTML Element 와 유사한 기능을 한다. +- JSXElement 를 충족시키기 위해서는 아래 목록 중 하나의 조건에 부합해야 한다. + +1. JSXOpeningElement : 특정 요소가 새롭게 열렸을 경우 쓰인다 (JSXClosingElement 와 한 쌍을 이뤄야 한다.) +2. JSXClosingElement : 앞선 요소가 열렸을 경우 이를 닫기 위해 쓰이는 형태다. +3. JSXSelfClosingElement : 요소가 시작되고 스스로 종료되는 형태이다. +4. JSXFragment : 특정 요소를 내포하고 있지 않지만, 주로 다른 JSX 를 그룹핑할 때 쓰인다. + +### ✏️ JSXElementName + +- JSXElementName 은 JSXElement 요소 명으로 쓰일 수 있는 것을 의미한다. + +1. JSXIdentifier : Javascript 내부에서 사용 가능한 식별자 +2. JSXNamespaceName : `:` 을 통해 두 개의 JSXIdentifier 를 이은 식별자. 3개 이상은 불가하다. +3. JSXMemberExpression: `.` 을 통해 여러 개의 JSXIdentifier 를 이은 식별자. + +```jsx +// JSXNamespaceName + + +// JSXMemberExpression + +``` + +### ✏️ JSXAttribute + +- JSXElement 에 부여 가능한 속성을 의미한다. +- 단순 속성을 의미하기에 **Require 한 값은 아니며** JSXElement 내부에서 존재하지 않아도 문법적인 오류는 아니다. + +1. JSXSpreadAttributes: JS 의 Spread Operator 를 사용하여 값을 할당할 때 쓰이는 표현식을 한번에 넣을 수 있는 방식이다. +2. JSXAttribute : 속성의 key 와 value 를 한 쌍으로 표현한 방식이다. 키는 JSXAttributeKey, 값은 JSXAttributeValue 로 불린다. + - `JSXAttributeValue` 에는 문자열 (큰 따옴표, 작은 따옴표) 및 다른 JSXElement 도 들어갈 수 있다. + - 추가로 `JSXAttributeValue` 에는 JS 에서 쓰이는 AssignmentExpression, 즉 값의 할당에 쓰이는 표현식도 추가할 수 있다. +3. JSXElement : JSX 의 속성으로 또 다른 JSXElement 를 추가할 수 있다. + - 보통 속성으로 JSXElement 를 추가할 때는 중괄호를 씌우는데, 이는 Prettier 의 설정으로 인한 변환이지 문법적으로는 없어도 된다. +4. JSXFragment : 별도의 속성을 가지지 않는 Fragment 또한 JSXElement 의 속성으로 추가될 수 있다. + +### ✏️ JSXChildren + +- JSX 는 속성을 가진 트리 구조를 표현하기 위해 제작되었으므로 부모 자식 관계를 나타낼 수 있는데, 이때 자식 관계에 놓인 Element 를 `JSXChildren` 라고 한다. +- JSXChildren 은 0개 이상의 JSXChild 로 구성되기 때문에 존재하지 않을 수도, 여러 개가 존재할 수도 있다. +- JSXChild 를 구성할 수 있는 요소의 목록은 아래와 같다. + +1. JSXText : `{`, `}`, `<`, `>` 를 제외한 나머지 문자열을 의미한다. JSX 문법과 혼동을 줄 수 있는 요소는 제외된다. (제외된 문자는 따옴표로 감싸서 문자열로 표현할 수 있다.) +2. JSXElement +3. JSXFragment +4. { JSXChildExpression } : JSXChildExpression 는 JS 의 AssignmentExpression 을 의미한다. + +```js +// JSXText 가 JSXChild 로 구성된 JSXElement. +<>JSXText 입니다. +<>{ '{}, <>' } // 제외된 문자도 아래처럼 사용 가능하다 + +// JSXChildExpression, JS 의 AssignmentExpression 에 해당되는 문법도 사용 가능하다. +<>{() => 'foo'} +<>{() => } // = <> +``` + +### ✏️ JSXString + +- HTML 에서 사용 가능한 문자열을 쉽게 JSX 에서도 쓸 수 있도록 고안된 장치이다. +- `\`, `"`, `'` 로 구성된 문자열은 별도의 이스케이프 방식을 사용하여 변환해야 한다. + +# ✒️ JSX 의 변환 과정 + +### ✏️ @babel/plugin-trasform-react-jsx + +- 기본적으로 JSX 는 ES 문법이 아니기 때문에 이를 표준 문법에 맞게 변환해주는 과정이 필요하다. +- Meta 에서는 babel 내 `@babel/plugin-trasform-react-jsx` plugin 을 사용하여 JSX 를 ReactElement 로 변환하는 방식을 채택한다. + +> 어떻게 babel 에서는 별도의 장치 없이 JSX 를 변환할 수 있게 된걸까? + +- React 17 이전에는 JSX 를 사용하는 모듈 최상단에 무조건 `import React from 'react'` 를 명시해야 했다. +- 이는 JSX 가 `React.createElement` 함수로 변환되기 때문에 **반드시 React 모듈을 참조해야 했기 때문**이다. +- 하지만 React 17 이후에는 JSX 를 모듈 내부에서 사용할 시, 트랜스 파일링 과정에서 `'react/jsx-runtime'` 모듈을 참조하도록 구조가 변경되었다. +- 이 덕에 개발자는 더 이상 JSX 를 사용하는 모듈 최상단에 React 를 import 해주지 않아도 된다. + +```jsx +const Component = ( + + Hello World + +); + +// React 17 이전 변환 결과 +// createElement(Component, props, children) 함수로 변환된 결과다. +var Component = React.createElement( + A, + { option: 'a', key: 'b' }, + 'Hello World' +); + +// React 17 이후 변환 결과 +import { jsx as _jsx } from 'react/jsx-runtime'; + +var Component = _jsx( + A, + { + option: 'a', + children: 'Hello World', + }, + { key: 'b' } +); +``` + +- 기존에는 Children 을 가변 인자 (3번째 인자 이후) 로 받았으나 이제는 props 로 받는다. +- 더 이상 props 에 key 를 포함하지 않고 별도의 인자로 넘거야 한다. +- 함수형 컴포넌트에서는 더 이상 defaultProps 을 사용할 수 없다. (딱히 필요하지도 않았고..) + +### ✏️ JSX 가 변환되는 특성을 사용하여 코드 리팩터링 + +- 기존에는 props 의 값에 따라 다른 컴포넌트를 렌더링 하도록 조건부 연산자를 사용했으나, 책에서는 아래와 같은 리팩터링 방식을 제안했다. +- 하지만 개인적인 의견으로는 굳이 `createElement` 를 사용하지 않고 아래처럼 렌더링 할 컴포넌트를 사전에 할당한 후 넘겨도 되지 않나 싶다. + +```tsx +// AS - IS : isHeading props 의 값에 따라 조건부 렌더링 진행 +const TestHeading = ({ isHeading, children }: PropsWithChildren) => { + return isHeading ?

{children}

:

{children}

+} + +// Book's Solution, createElement 를 직접 사용하여 렌더링 주체 변경 +const TestHeading = ({ isHeading, children }: PropsWithChildren) => { + return createElement(isHeading ? 'h1' : 'p', {}, children)' +} + +// My Solution : 굳이 createElement 을 사용하지 않고 렌더링 주체 (JSXElement) 변경 +const TestHeading = ({ isHeading, children }: PropsWithChildren) => { + const RenderComponent = isHeading ? 'h1' : 'p' + return {children} +} +``` + +# ✒️ VDOM 과 React Fiber + +### ✏️ DOM 과 브라우저 렌더링 과정 + +DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접근할 수 있도록 설계된 Object 다. + + +> 브라우저에서 특정 화면을 보여주기 위한 렌더링 과정은 아래와 같이 작성할 수 있다. + +1. 서버로부터 받은 HTML 파일을 다운로드 하여 브라우저 렌더링 엔진이 이를 파싱하도록 한다. +2. 파싱 결과로 나온 DOM 노드를 조립하여 DOM 트리를 구축한다. +3. 서버로부터 CSS 파일을 인계 받는다면 이를 파싱하여 CSSOM 을 만들고, 조립하여 트리를 구축한다. +4. 생성된 DOM 트리 내 요소 중에서 사용자에게 시각적으로 보여지는 요소를 선별한다. +5. 선별된 DOM 노드를 순회하여 매칭되는 CSSOM 노드를 적용한다. 이렇게 합쳐진 트리를 Render Tree 라 한다. +6. 렌더 트리의 루트부터 각 노드를 순회하며 해당 노드가 화면에 어느 위치에 놓여야 하는지를 계산한다. 이를 Layout 과정이라 한다. +7. 레이아웃 과정이 완료되었다면 렌더 트리의 루트부터 순차적으로 화면에 페인팅을 시작한다. 이를 Painting 과정이라 부른다. + +- 과거 브라우저 렌더링 관련 정리 글 : https://velog.io/@rookieand/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-%EA%B1%B8%EA%B9%8C + +### ✏️ VDOM 의 탄생 배경 + + +1. 비싼 렌더링 비용 +- 특정 요소의 스타일 혹은 내부 요소의 변경이 일어날 경우 브라우저는 렌더링 과정을 재수행하게 된다. +- 이러한 과정은 **Reflow** 와 **Repaint** 로 나뉘는데, Reflow 의 경우 필연적으로 Repaint 작업까지 수행하기에 **렌더링 비용이 비싸다.** + +2. SPA 의 특수한 경우 +- 싱글 페이지 애플리케이션 (SPA) 의 경우 하나의 화면에서 각 요소를 다시 설계하고 그려야 하기 때문에 DOM 을 관리하는데 드는 비용이 높다. + +> 이러한 문제점을 해결하기 위해 등장한 개념이 바로 가상 돔 (Virtual DOM) 이다. + +`react-dom` 에서 관리하는 Virtual DOM Tree 를 생성하여 메모리에 적재하고, React 에서 DOM 의 변경 사항을 Trigger 할 경우 VDOM 에 우선 반영하여 변경에 대한 준비가 완료될 경우 실제 DOM Tree 에 변경 사항을 Update 한다. + +- VDOM 은 절대로 일반 DOM 을 수정하는 것보다 **결코 빠르지 않다**. 오히려 렌더링을 진행하기 위한 과정이 추가된 셈이라 상황에 따라서는 더 성능이 느릴 수 있다. +- 최근 등장한 웹 프레임워크 (SolidJS, Svelte) 의 경우에는 실제 DOM 을 수정하는 방식을 채택하여 Virtual DOM 이 가진 한계를 타파하는 모습을 보였다. + + +### ✏️ Reconciliation + +컴포넌트 내부에 변경 사항이 발생하여 리렌더링이 진행될 경우, 기존의 DOM Tree 와 새롭게 구축된 DOM Tree 간의 비교 사항을 알아야 할 필요가 있다. + +두 트리 간의 변경 사항을 비교하고 만약 변경 사항이 존재한다면 이를 실제 DOM Tree 에 적용하는 과정을 거치는데, 이때 두 트리를 비교하는 과정을 **재조정 (Reconciliation)** 이라 한다. + +다만 N 개의 노드가 존재하는 두 트리 간의 비교를 위해서는 `O(n^3)` 의 복잡도가 들기 때문에 React 에서는 몇 가지 가정을 통한 휴리스틱 알고리즘을 세워 복잡도를 `O(n)` 으로 낮췄다. + +1. Element 의 타입이 달라질 경우 해당 Element 는 서로 다른 트리를 구성한다. +2. **key props** 가 변경될 경우 해당 Element 는 서로 다른 트리를 구성한다. + + +### ✏️ React Fiber 의 등장 계기 + +기존 재조정 알고리즘의 구조는 Stack 기반이었기 때문에 해당 스택에 필요한 작업을 적재시키고 순차적으로 이를 해결하는 방식을 채택했다. + +과거에는 해당 Stack 에 들어간 **여러 개의 작업을 하나로 묶어 동기적으로 이를 진행**했기에 때문에 중간의 작업을 취소하거나 작업 간의 우선 순위를 변경할 수 없었고, 중간에 작업이 지연될 경우 브라우저 렌더링도 같이 지연되는 문제가 있었다. + +즉, 여러 작업 간의 우선 순위를 깡그리 무시한 채 화면에 변경 사항을 적용하는 일련의 과정을 **하나의 큰 태스크로 놓고 실행하던 것**이 기존의 Stack Reconciliation 의 문제였다. + +- 사용자의 인터렉션에 기반한 화면 변경을 유발하는 작업이 짧은 시간 내에 다수 발생한다고 가정해보자. (Input 에 키워드를 입력하면 검색 목록이 나오는 경우) +- API 호출에 대한 응답 처리는 비교적 후순위로 두어도 사용자의 UX 경험에는 지장이 없으나, Input 의 변경 사항은 빠르게 실행되어야 한다. +- 이는 Call Stack 에 많은 양의 작업을 적재시킬 것이고, 우선 순위가 낮은 작업도 모두 완료가 되어야지만 브라우저 렌더링이 재개되기에 사용자로 하여금 UX 를 저하시키는 요인이 된다. + +따라서 이러한 문제를 해결하기 위해 React 16 버전 이상부터는 기존의 Stack 방식이 아닌 **React Fiber** 라는 새로운 아키텍쳐가 도입되었다. + +### ✏️ React Fiber + +- React Fiber 는 각각의 작업에 대한 우선 순위를 매기고 이에 대한 일시 정지, 재가동, 우선 가동을 가능하도록 설계되었다. 이러한 방식을 **incremental rendering** 이라 한다. +- \ No newline at end of file