JD의 블로그

Learning React - 4장 본문

Web/프론트엔드

Learning React - 4장

GDong 2021. 10. 6. 23:29

리액트의 작동 원리

2장에서는 최신 자바스크립트 문법을 배웠고 3장에서는 리액트의 탄생을 이끈 함수형 프로그래밍 패턴을 리뷰했다. 

이번 장에서는 리액트가 어떻게 작동하는지 배울 것이다. 

 

리액트를 사용할 때는 JSX로 앱을 만들 가능성이 크다. 그리고 정말 리액트를 이해하고 싶다면 가장 핵심이 되는 단위인 리액트 엘리먼트를 이해할 필요가 있다. 

 

1. 페이지 설정

리액트를 브라우저에서 다루려면 React와 ReactDOM 라이브러리를 불러와야 한다. React는 뷰를 만들기 위한 라이브러리고 ReactDOM은 UI를 실제로 브라우저에 렌더링할 때 사용하는 라이브러리다. 

 

2. 리액트 엘리먼트

HTML는 브라우저가 문서 객체 모델인 DOM(Document Object Model)을 구성하기 위해 따라야 하는 절차라고 간단히 말할 수 있다. HTML 문서를 이루는 엘리먼트는 브라우저가 HTML 문서를 읽어들이면 DOM 엘리먼트가 되고, 이 DOM이 사용자 인터페이스를 화면에 표시한다. 

 

전통적으로 웹사이트는 독립적인 HTML 페이지들로 만들어졌다. 사용자가 페이지 사이를 내비게이션 함에 따라 브라우저는 매번 다른 HTML 문서를 요청해서 로딩할 수 있었다. AJAX(Asynchronous JavaScript and XML)이 생기면서 단일 페이지 애플리케이션이 생겼다. 브라우저가 AJAX를 이용해 아주 작은 데이터를 요청해서 가져올 수 있게 됨에 따라 이제는 전체 웹 애플리케이션이 한 페이지로 실행되면서 자바스크립트에 의존해 사용자 인터페이스를 갱신했다.

 

SPA에서 처음에 브라우저는 HTML 문서 하나를 적재한다. 사용자는 사이트를 내비게이션하지만 실제로는 같은 페이지 안에 계속 머문다. 자바스크립트는 사용자가 애플리케이션과 상호 작용하는 것에 맞춰 표시중이던 인터페이스를 없애고 새로운 사용자 인터페이스를 만든다. 

 

DOM API는 브라우저의 DOM를 변경하기 위해 자바스크립트가 사용할 수 있는 객체의 모음이다. document.createElement나 document.appendChild를 써봤다면 이미 DOM을 써본 것이다. 화면에 표시된 DOM 엘리먼트를 자바스크립트로 갱신하거나 변경하기는 상대적으로 쉽다.

 

리액트는 브라우저 DOM을 갱신해주기 위해 만들어진 라이브러리다. 리액트가 모든 처리를 대신 해주기 때문에 더 이상 SPA를 더 효율적으로 만들기 위해 복잡한 내용을 신경쓸 필요가 없다. 리액트에서는 코드로 DOM API를 직접 조작하지 않는다. 대신 리액트에게 어떤 UI를 생성할지 지시하면, 리액트가 명령에 맞춰 원소 렌더링을 조절한다. 

 

브라우저 DOM이 DOM 엘리먼트로 이뤄지는 것처럼 가상 DOM은 리액트 엘리먼트로 이뤄진다. 리액트 앨리먼트는 개념상 HTML 앨리먼트와 비슷하지만 실제로는 자바스크립트 객체이다. 우리가 가상 DOM을 변경하면 리액트는 DOM API를 통해 그 변경 사항을 가장 효율적으로 랜더링해준다.

 

리액트 엘리먼트는 그에 대응하는 실제 DOM 엘리먼트가 어떻게 생겨야 하는지를 기술한다. 즉 리액트 엘리먼트는 브라우저 DOM을 만드는 방법을 알려주는 명령이다. 

 

React.createElement를 사용해 h1을 표현하는 리액트 엘리먼트를 만들 수 있다.

React.createElement("h1", {id: "recipe-0"} "구운 연어")

첫 번째 인자는 만들려는 엘리먼트의 타입을 정의한다. 여기서는 h1 엘리먼트를 만든다. 두 번째 인자는 엘리먼트의 프로퍼티를 표현한다. 여기서 만드는 h1에는 recipe-0이라는 id가 있다. 세 번째 인자는 만들려는 엘리먼트를 여는 태그와 닫는 태그 사이에 들어가야 할 자식 노드들을 표현한다.

 

렌더링 과정에서 리액트는 이 엘리먼트를 실제 DOM 엘리먼트로 변환한다. 

<h1 id="recipe-0">구운 연어</h1>

 

리액트 엘리먼트는 이렇게 생겼다. 단지 리액트에게 DOM 엘리먼트를 구성하는 방법을 알려주는 자바스크립트 리터럴에 불과하다.

{
	$$typeof: Symbol(React.element),
    "type": "h1",
    "key": null,
    "ref": null,
    "props": {id: "recipe-0", children: "구운 연어"},
    "_owner": null,
    "_store": {}
}

리액트 엘리먼트의 type 프로퍼티는 만들려는 HTML이나 SVG 엘리먼트의 타입을 지정한다. props 프로퍼티는 DOM 엘리먼트를 만들기 위해 필요한 데이터나 자식 엘리먼트들을 표현한다. children 프로퍼티는 텍스트 형태로 표시할 다른 내부 엘리먼트이다.

 

주의 : React.createElement가 반환하는 객체를 살펴보았는데 이와 같은 리터럴을 손으로 입력해서 엘리먼트를 만드는 경우는 없다. 리액트 엘리먼트를 만들고 싶다면 항상 React.createElement 함수를 사용해야 한다.

 

3. ReactDOM

리액트 엘리먼트를 만들면, 브라우저에서 보고 싶을 것이다. ReactDOM에는 리액트 엘리먼트를 브라우저에 렌더링하는데 필요한 모든 도구가 들어있다. ReactDOM에는 render 메서드가 들어있다. 

 

리액트 엘리먼트와 그 모든 자식 엘리먼트를 함께 렌더링하기 위해 ReactDOM.render를 사용한다. 이 함수의 첫 번째 인자는 렌더링할 리액트 엘리먼트이며, 두 번째 인자는 렌더링이 일어날 대상 DOM 노드이다.

 

var dish = React.createElement("h1", null, "구운 연어");
ReactDOM.render(dish, document.getElementById('root'));

모든 DOM 렌더링 기능은 ReactDOM 패키지에 들어있다. 요즘은 배열을 렌더링할 수도 있다. 

const dish = React.createElement("h1", null, "구운 연어");
const dessert = React.createElement("h2", null, "코코넛 크림 파이");

ReactDOM.render([dish,dessert], document.getElementById("root"));

이 코드는 root 컨테이너 내부에 이 두 엘리먼트를 형제 노드로 렌더링한다. 

 

3.1 Children

텍스트가 아닌 다른 리액트 엘리먼트들을 자식으로 렌더링할 수도 있고 그렇게 하면 엘리먼트의 트리가 생긴다.

React.createElement(
	"ul",
    null,
    React.createElement("li",null,"연어 900 그램"),
    React.createElement("li",null,"신선한 로즈마리 5가지"),
    React.createElement("li",null,"올리브 오일 2 테이블스푼"),
    React.createElement("li",null,"작은 레몬 2조각"),
    React.createElement("li",null,"코셔 소금 1 티스푼"),
    React.createElement("li",null,"다진 마늘 4쪽")
);

 

리액트에서 className 사용하기

HTML class 속성이 있는 엘리먼트는 class 대신 className이라는 이름의 프로퍼티를 사용해야 한다. class가 자바스크립트에서 예역어라서 HTML 엘리먼트의 class를 정의하려면 어쩔 수 없이 className을 사용해야만 한다.

 

 

데이터를 가지고 엘리먼트 만들기

리액트를 사용하는 경우 큰 장점은 UI 엘리먼트와 데이터를 분리할 수 있다는 점이다. 리액트는 단순한 자바스크립트이기 때문에 리액트 컴포넌트 트리를 더 편하게 구성하기 위해 자바스크립트 로직을 얼마든지 추가할 수 있다. 예를 들어 배열에 재료를 저장해두고 그 배열을 리액트 엘리먼트로 map 할 수 있다.

 

const items = [
	"연어 900 그램",
    "신선한 로즈마리 5가지",
    "올리브 오일 2 테이블 스푼"
];

React.createElement(
	"ul",
    { className: "ingredients"},
    items.map(ingredient => React.createElement("li", null, ingredient)
);

이 코드를 실행하면 경고가 발생하는데 배열을 이터레이션해서 자식 엘리먼트의 리스트를 만드는 경우 리액트에서는 각 자식 엘리먼트에 Key 프로퍼티를 넣는 것을 권장하기 때문이다. 리액트는 key를 사용해 DOM을 더 효율적으로 갱신할 수 있다. 

React.createElement(
	"ul",
    { className: "ingredients" },
    items.map((ingredient, i) => 
    	React.createElement("li", {key: i}, ingredient)
     )
 );

 

4. 리액트 컴포넌트 

사용자 기술이나, 내용, 크기와 관계없이 모든 사용자 인터페이스는 여러 부분으로 이뤄진다. 

버튼, 리스트, 제목 등이 이런 부품이다. 이런 부품이 모여서 사용자 인터페이스를 이룬다. 리액트에서 이런 각 부분을 컴포넌트라고 부른다. 컴포넌트를 사용하면 서로 다른 데이터 집합에 대해 같은 DOM 구조를 재사용할 수 있다. 

리액트로 만들고 싶은 사용자 인터페이스에 대해 생각할 때는 엘리먼트를 재사용 가능한 조각으로 나눌 수 있는지 고려해보면 좋다. 

이때 얼만큼 이 구조가 규모 확장성이 있는지 생각해보면 좋다.

 

여기서는 함수를 작성해 컴포넌트를 만든다. 함수는 사용자 인터페이스에서 재활용할 수 있는 부품을 반환한다. 

const secretIngredients = [
	"무염 버터 1컵",
    "크런치 땅콩 버터 1컵",
    "흑설탕 1컵"
];

function IngredientsList() {
	return React.createElement(
    	"ul",
        { className: "ingredients" },
        items.map((ingredient, i) => 
        	React.createElement("li", {key: i}, ingredient)
        )
    );
}

ReactDOM.render(
	React.createElement(IngredientsList, {items: secretIngredients}, null),
    document.getElementById("root")
);

이런식으로 컴포넌트를 만들면 컴포넌트가 더 유연해진다. items 배열에 원소가 하나뿐이든 수백개 이든 컴포넌트는 배열의 각 원소를 리스트 원소로 렌더링할 것이다.

 

여기서 items 배열을 리액트 프로퍼티로 참조하게 변경할 수도 있다. 전역 items에 대해 매핑을 수행하는 대신 props 객체를 통해 items를 얻게할 수 있다.

function IngredientsList(props){
	return React.createElement(
    	"ul",
        {className: "ingredients"},
        props.items.map((ingredient, i) =>
        	React.createElement("li", {key: i}, ingredient)
        )
     );
 }

 

'Web > 프론트엔드' 카테고리의 다른 글

Learning React - 6장  (0) 2021.10.11
Learning React - 5장  (0) 2021.10.08
Learning React - 3장  (0) 2021.10.06
Learning React - 2장  (0) 2021.10.04