평소 CRA(create-react-app) 명령어를 통해 프로젝트 초기설정을 하였다. 간편하게 초기설정을 할 수 있어서 편리했지만 React가 실제로 JavaScript 내에서 어떻게 사용되어지고 어떤 원리로 작동하는지에 대해 궁금했다. 관련 궁금증을 해결하기 위해 Learning React 러닝 리액트 2판(한빛미디어 출판)을 통해 학습하는 도중 정리의 필요성을 느껴 해당 글을 작성한다.
CDN으로 페이지 설정하기
기본적으로 React를 사용하여 페이지를 구성하는 것을 이해하기 위해서는 React는 JSX 문법을 사용하여 React element(리액트 엘리먼트)를 만든다는 내용을 이해해야한다. 우선적으로, 리액트 엘리먼트를 만들고 렌더링 하기 위해서는 React, ReactDOM 라이브러리를 사용해야한다. React의 createElement 메서드를 이용하여 리액트 엘리먼트를 만들고 ReactDOM의 render 메서드를 이용하여 해당 리액트 엘리먼트를 렌더링하여 화면에 보여준다. index.html 파일을 만들고 CDN을 이용하여 기본 페이지를 만들어 보자.
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>순수 리액트 예</title>
</head>
<body>
<div id="root"></div>
<script
crossorigin
src="https://unpkg.com/react@17/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"
></script>
<script>
// JavaScript code..
</script>
</body>
</html>
위와 같이 코드를 작성하면 리액트를 이용하여 페이지를 구성할 기본적인 사항은 갖춘 것이다. 여기서 주의할 점은 JavaScript 코드를 작성하기 전에 리액트 라이브러리 관련 CDN 코드를 작성해야 한다는 점이다. CDN 링크는리액트 공식 홈페이지에서 확인할 수 있다.
React Element
React.createElement 메서드를 사용하여 기본적으로 리액트 엘리먼트를 만들고 ReactDOM.render 메서드를 사용하여 렌더링한다. 아래와 같이 코드를 작성하면 페이지를 렌더링 할 수 있다.
//index.html
//..
<script>
const dish = React.createElement("h1", { id: "recipe-0" }, "구운 연어");
ReactDOM.render(dish, document.getElementById("root"));
</script>
//..
React.create 메서드는 첫번째 인자로 해당 엘리먼트의 타입을, 두번째 인자로 해당 엘리먼트의 프로퍼티를, 세번째 인자부터는 자식노드들을 포함할 수 있다. 자식 노드들이 들어가는 자리는 태그와 태그 사이의 내용을 나타내는 텍스트 형식을 입력해도 되지만 태그를 삽입하여 자식 노드를 생성할 수 도 있다. ReactDOM.render 메서드의 첫번째 인자는 리액트 엘리먼트를, 두번째 인자로 렌더링이 일어나야할 실제 DOM의 노드이다.
크롬 개발자 도구를 이용하여 확인하면 위의 코드를 통해 실제 생성된 DOM 트리는 다음과 같다는 것을 알 수 있다.
위에서 언급했다시피, React.createElement 메서드의 세번째 인자 부터 자식 노드들을 인자로 받아들일 수 있다고 했다. 다음과 같은 특성을 이용하여 자식 노드들을 포함한 페이지를 만들어보자.
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>순수 리액트 예</title>
</head>
<body>
<div id="root"></div>
<script
crossorigin
src="https://unpkg.com/react@17/umd/react.production.min.js"
></script>
<script
crossorigin
src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"
></script>
<script>
const list = 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 쪽")
);
ReactDOM.render(list, document.getElementById("root"));
</script>
</body>
</html>
해당 코드를 브라우저에서 확인하면 다음과 같은 페이지를 볼 수 있다.
개발자 도구를 살펴보면 다음과 같이 ul 엘리먼트의 자식 노드로 li 엘리먼트들이 생성된 것을 볼 수 있다. 이렇게 작성하면 리액트 엘리먼트를 이용하여 다양한 노드들을 만들어내고 실제 DOM에 화면을 렌더링 할 수 있다.
한편, 리액트의 장점중 하나는 UI 요소와 데이터를 분리하여 작성할 수 있다는 것이다. UI 요소와 데이터를 분리해보자.
//index.html
//..
<script>
const items = [
"연어 900 그램",
"신선한 로즈마리 5 가지",
"올리브 오일 2 테이블 스푼",
"작은 레몬 2 조각",
"코셔 소금 1 티스푼",
"다진 마늘 4 쪽",
];
const list = React.createElement(
"ul",
{ className: "ingredients" },
items.map((ingredient, index) =>
React.createElement("li", { key: index }, ingredient)
)
);
ReactDOM.render(list, document.getElementById("root"));
</script>
//..
Array.prototype.map 함수를 사용하여 items 배열을 순회하며 해당 데이터들을 화면에 렌더링 한다. 이 부분은 리액트가 함수형 프로그래밍에 기반하여 코드를 작성할 수 있다는 특징을 잘 보여준다. 또한 <li> 리액트 엘리먼트를 만들 때, 프로퍼티 값으로 key 값을 부여했다는 것에 주목할 필요가 있다. 이처럼 어떠한 데이터를 반복적으로 나타낼때는 고유한 key 값을 사용해야한다.(해당 부분은 리액트의 Reconciliation 재조정과 관련이 있다. 해당 내용 관련 공식문서를 읽어보길 바란다.)
React Component
컴포넌트를 이용하여 페이지를 구성해보자. 컴포넌트는 작은 조각 및 부품을 뜻하며 공통적으로 사용할 수 있는 부분을 부품화하는 것을 통해 DOM 구조를 재사용할 수 있다는 장점을 갖고있다. 레시피를 만든다고 했을때, 아래와 같은 화면을 구성한다고 하면 대략적으로 레시피 제목, 재료, 조리 방법 등을 컴포넌트화 하여 재사용할 수 있을 것이다. 컴포넌트를 활용하여 코드를 작성해보자.
리액트에서 함수형 컴포넌트가 등장하기 이전에는 클래스형 컴포넌트를 사용했다. 하지만 여기서는 함수형 컴포넌트만을 다룰 예정이다. 함수형 컴포넌트를 사용해서 코드를 작성해보자.
//index.html
// ..
<script>
const secretIngredients = [
"연어 900 그램",
"신선한 로즈마리 5 가지",
"올리브 오일 2 테이블 스푼",
"작은 레몬 2 조각",
"코셔 소금 1 티스푼",
"다진 마늘 4 쪽",
];
function IngredientList() {
return React.createElement(
"ul",
{ className: "ingredients" },
secretIngredients.map((ingredient, index) =>
React.createElement("li", { key: index }, ingredient)
)
);
}
ReactDOM.render(
React.createElement(IngredientList, { items: secretIngredients }),
document.getElementById("root")
);
</script>
// ..
리액트 엘리먼트를 반환하는 IngredientList 함수형 컴포넌트를 만들었다. 해당 컴포넌트는 secretIngredients 배열을 순회하면서 각각의 재료들을 li 엘리먼트로 렌더링 해준다. 성공적으로 레시피 재료 관련 리액트 엘리먼트를 반환하는 함수형 컴포넌트를 만들었다. 하지만 재사용 관점에서 보았을때 해당 코드는 재사용이 힘들다. 현재의 IngredientList 컴포넌트는 구운 연어만을 만들 수 있는 코드로 재사용할 수 없는 코드이며 각각의 레시피 마다 필요한 재료가 다를 것이기 때문이다. 해당 컴포넌트를 재사용하기 위해서는 배열 데이터로 주어지는 재료 데이터를 인자로 받아 각각의 레시피에 맞게 재료 목록을 반환해줄 필요가 있다. 이 내용을 적용하여 컴포넌트의 코드를 변경해보면 다음과 같다.
//index.html
//..
<script>
//..
function IngredientList(props) {
return React.createElement(
"ul",
{ className: "ingredients" },
props.items.map((ingredient, index) =>
React.createElement("li", { key: index }, ingredient)
)
);
}
ReactDOM.render(
React.createElement(IngredientList, { items: secretIngredients }),
document.getElementById("root")
);
</script>
//..
// 구조분해를 적용하면 조금 더 간결하게 사용할 수 있다.
//..
<script>
//..
function IngredientList({ items }) {
return React.createElement(
"ul",
{ className: "ingredients" },
props.items.map((ingredient, index) =>
React.createElement("li", { key: index }, ingredient)
)
);
}
ReactDOM.render(
React.createElement(IngredientList, { items: secretIngredients }),
document.getElementById("root")
);
</script>
//..
이렇게 재사용이 가능한 함수형 컴포넌트를 만들어 보았다. 직접 index.html 파일에 리액트 코드를 작성해보면서 리액트 엘리먼트를 생성하고 함수형 컴포넌트를 만드는 과정을 알 수 있었고 평소 관심을 갖지 않았던 리액트 엘리먼트에 대해 이해할 수 있었다. 리액트 엘리먼트는 단순한 JavaScript 객체이며, props, children, key, $$typeof 등의 프로퍼티를 갖는다. 리액트 엘리먼트를 콘솔에 찍어보지 않았다면 한 번 찍어보자.
'FRONTEND' 카테고리의 다른 글
[React] Learning React Chapter 5 - 웹팩과 바벨 part 2 (0) | 2022.08.22 |
---|---|
[React] Learning React Chapter 5 - 웹팩과 바벨 part 1 (0) | 2022.08.22 |
[React] 클로저와 React Hooks(feat. 스코프) (0) | 2022.08.16 |
[JavaScript] 이벤트 위임 (0) | 2022.08.01 |
[React] React는 왜 사용할까?(+ Redux는 왜 사용할까?) (0) | 2022.08.01 |