september 프로젝트의 프론트엔드 애플리케이션

리액트를 구성하는 핵심 개념은 JSX, Virtual DOM, Component이다.

개발환경 구성

node, npm 설치

brew install node

타입스크립트 컴파일러 설치

$ npm i -g typescript ts-node

Neovim setup for React, TypeScript, Tailwind CSS

프로젝트 생성

타입스크립트 기반 리액트 프로젝트 생성

$ npx create-react-app <프로젝트 이름> --template typescript

DOM

웹 브라우저는 HTML 문자열을 parsing하여 자바스크립트 객체 조합(DOM tree)으로 변환한다. (Chromium 프로젝트에서는 Blink라는 렌더링 엔진이 DOM tree를 만들고 DOM 객체를 렌더링한다.) DOM tree를 이루는 객체들은 JavaScript 객체이며 HTML 태그에 맞는 인터페이스를 구현한다. 이 인터페이스들을 DOM이라고 총칭한다.

웹 브라우저의 JS 엔진은 window라는 이름의 전역 변수를 기본적으로 제공한다. window는 웹 브라우저의 특정 웹 페이지를 추상화한 객체이다. 이 객체는 Window타입이다. 이 타입을 BOM(Browser Object Model)이라고 한다.

Physical DOM

물리 DOM을 사용해서 웹 페이지를 만들 때에는 JS만 사용하면 된다.

const pPhysicalDOM = document.createElement('p')
pPhysicalDOM.innerText = 'Hello world'
document.body.appendChild(pPhysicalDOM)

document.createElement 함수로 생성된 DOM 객체를 document.body.appendChild로 DOM tree에 추가하면 해당 DOM 객체가 렌더링을 거쳐 바로 화면에 출력된다.

Virtual DOM

React를 사용하면 아래와 같은 방법으로 웹 페이지를 만들 수 있다.

import React from 'react'
import ReactDOM from 'react-dom/client'

const pVirtualDOM = React.createElement('p', null, 'Hello world')
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

root.render(pVirtualDOM)
  • React.createElement() - ReactElement타입의 Virtual DOM을 생성한다. 이 객체는 document.body.appendChild함수의 인자로 넘길 수 없다.
  • ReactDOM.createRoot(document.getElementById('root') as HTMLElement) - 가상 DOM 트리를 부착(append)할 물리 DOM 객체가 필요하다.
  • root.render() - 가상 DOM 트리를 물리 DOM 트리로 변환한다.

JSX

JSX(JavaScript XML)는 XML에 자바스크립트 코드를 결합하기 위한 문법이다. JSX는 React.createElement 함수 호출을 간결하게 할 목적으로 만들어졌다.

const hello = 'Hello world'
<p>
  {hello}
</p>

중괄호에는 자바스크립트 표현식만을 넣을 수 있다. 표현식(expression)이란 return 키워드 없이 값을 반환하는 구문이다. 프로그래밍 언어에서 표현식이란 `1` `true` `'Hello world'`처럼 값으로 평가되는 것들이다. 반대되는 개념은 실행문(statement)이다. 실행문은 특정 동작을 수행하며 값을 반환하지 않을 수도 있다.

JSX 구문은 단순화된 React.createElement 호출이므로 ReactElement타입의 객체를 반환한다. 그래서 변수나 배열에 저장이 가능하다.

const children = [
  <li>
    <a href="http://www.google.com" target="_blank">
      <p> go to google </p>
    </a>
  </li>, 
  <li>
    <a href="http://www.naver.com" target="_blank">
      <p> go to naver </p>
    </a>
  </li>
]

const rootVDOM = <ul>{children}</ul>

const root = ReactDOM.creatRoot(document.getElementById('root') as HTMLElement)
root.render(rootVDOM)

JSX는 자신을 감싸는 부모 요소 없이는 존재할 수 없다. 예를 들어 root.render({children}) 은 오류를 발생시킨다.

Component

컴포넌트에는 두 종류가 있다. 클래스 컴포넌트와 함수 컴포넌트이다. 컴포넌트라는 개념은 Smalltalk에서 유래했다. Smalltalk에서 컴포넌트는 화면 UI를 처리하는 클래스이다.

리액트 팀은 가능한 한 함수 컴포넌트와 리액트 훅을 사용할 것을 권장한다.

Class Component

클래스 컴포넌트는 반드시 react 패키지가 제공하는 Component 클래스를 상속하고 render라는 메서드를 정의(Overriding)해야 한다.

import React, {Component} from 'react'
export default class ClassComponent extends Component {
    render() { return null }
}

일반적으로 render메서드는 React.createElement 함수의 반환값 또는 JSX문 등으로 Virtual DOM 객체를 반환한다. (JSX문은 부모 요소로 감싸서 반환한다.)

속성(Property)

리액트에서 속성이란 부모 컴포넌트가 자식 컴포넌트에게 전달하는 정보(status)이다.


function createElement<P extends {}>(
    type: FunctionComponent<P> | ComponentClass<P> | string, pops?: Attritubtes & P | null,
      ...children: ReactNode[]): ReatcElement<P>;
)

Event

웹 브라우저의 자바스크립트 엔진이 제공하는 속성에는 다음과 같은 것들이 있다.

  • type - 이벤트 이름(대소문자 구분 없음)
  • isTrusted - 웹 브라우저에서 발생한 이벤트인 경우 true 프로그래밍으로 발생한 이벤트인 경우 false
  • target - 이벤트가 처음으로 발생한 HTML 요소
  • bubbles - bubbling 여부 결정

Event type

임의의 DOM 객체가 Event를 받은 경우 그 이벤트가 DOM 트리를 타고 부모 DOM 객체를 따라 root까지 전파되는 현상이다. 전파(propagation)를 명시적으로 막지 않으면 bubbling이 발생한다.

EventTarget

HTMLELement hierarchy

EventTargeT 인터페이스에는 addEventListener removeEventListener dispatchEvent 메서드가 정의되어 있다. addEventListener 메서드를 통해 이벤트를 처리할 event handler를 등록할 수 있다.

window.addEventListener('click', (e: Event) => console.log("mouse click event occured"))

리액트 프로젝트는 항상 public 디렉토리의 index.html 파일에 <div id="root"> 태그를 포함하고 있다.

document.getElementById('root')?.addEventListener('click', (e: Event) => {
    const {isTrusted, target, bubbles} = e
    console.log('mouse click occured.', isTrusted, target, bubbles)
})

BaseSyntheticEvent

Physical DOM을 사용하는 경우 onclick 속성 또는 addEventListener를 통해 event handler(콜백)를 등록하지만 Virtual DOM을 사용하는 리액트에서는 리액트 컴포넌트의 이벤트 속성을 사용한다. 리액트컴포넌트의 이벤트 속성에 설정하는 콜백 함수는 매개변수 e의 타입이 Event가 아니라 SyntheticEvent이다.

interface BaseSyntheticEvent<E = object, C = any, T = any> {
    nativeEvent E;
    currentTarget: C;
    target: T;
    preventDefault(): void;
    stopPropagation(): void;
}
  • nativeEvent - 리액트에서는 물리 DOM에서 발생하는 이벤트를 native event라고 한다.
  • currentTarget - Bubbling 과정에서 현재 이벤트를 받은 DOM 객체
  • target - event를 최초로 받은 DOM 객체

optional chaining

?.는 옵셔널 체이닝 연산자이다. 위의 코드에서 document.getElementById('root') 메서드가 null값을 반환하면 addEventListener 메서드는 호출되지 않는다.

React를 사용하는 이유

  • 컴포넌트 - 코드를 재사용할 수 있다. UI를 독립적으로 관리할 수 있다.
  • Virtual DOM - 빠른 렌더링이 가능하다. 물리 DOM 변경을 최소화한다.
  • Hooks - 로직의 재사용, 컴포넌트의 분리가 용이하다.

reference

Comments