리액트는 페이스북 개발팀이 만들어 2013년 오픈소스로 세상에 공개했다. 페이스북은 출시 초기부터 페이스북은 다른 웹 서비스 보다 유독 인터렉션(Interaction)이 많아서 이를 잘 수행하기 위해 react를 만들었다.
( 인터렉션: 좋아요 버튼이나 채팅처럼 사용자와 웹 서비스 간에 양방향으로 소통하는 기능을 지칭)
웹 서비스를 쉽고 빠르게 개발할 수 있는 Node.js의 라이브러리 가운데 하나로 리액트를 한마디로 표현하면 "서비스의 변화가 많고 사용자와 상호작용이 원활한 대규모 웹 애플리케이션을 쉽게 구축할 수 있는 기술"이다. 실제로 리액트는 페이스북, 인스타그램, 넷플릭스 등 서비스를 개발한 기술에 사용된다.
< 리액트의 3가지 특징 >
1. 컴포넌트 기반의 유연성
2. 쉽고 간단한 업데이트
3. 빠른 업데이트
1- (1) 컴포넌트 기반의 유연성
- 리액트는 유연성이 있어서 새로운 기능을 추가하거나 업그레이드할 때 코드를 많이 수정하지 않아도 된다.
< 유연성이 없을때 어떤 문제 >
- 중복 코드와 유연하지 못한 구조
페이지를 추가할 때 <header> 태그 요소는 기존 페이지의 소스 코드에서 복사하여 붙여 넣고, <article> 태그에서 해당 페이지에 필요한 요소만 새롭게 작성하면 된다. 동일한 내용을 여러 번 작성해야 하는 코드를 흔히 중복 코드라고 하고, <header> 태그에서 수정이 생기면, 모든 페이지에서 이 태그를 변경해야 한다. 중복코드가 많이 발생하면 "유연하지 못하다."라고 말하고 페이지의 수가 많고, 기능 수정 또는 추가가 잦은 대규모 애플리케이션을 구축할 때는 적합하지 않다.
=> 리액트는 모듈화를 이용해 중복 코드를 제거한다.
(= 여러 페이지에서 공통으로 사용하는 코드를 ‘컴포넌트’ 단위의 모듈로 만들어 놓고 필요할 때 호출해 사용한다. )
< 컴포넌트 개념 >
- ‘페이지를 구성하는 요소’, HTML 요소를 반환하는 함수
//<header> 태그를 컴포넌트로 만들면
function MyHeader() {
return (
<header>
<h1>안녕하세요 수정된 이정환입니다.</h1>
</header>
);
}
<!DOCTYPE html>
<html>
<body>
<!-- MyHeader.js에서 불러온 header 요소 -->
<MyHeader /> ①
<article>
<h3>여기는 HOME입니다</h3>
</article>
</body>
</html>
<header> 태그가 필요한 페이지가 있다면 언제든지 이 컴포넌트를 불러와 사용하면 된다. Header 컴포넌트에 변경 사항이 있어 코드를 수정하면, 일괄적으로 모든 페이지에 자동으로 반영되므로 기능 하나를 수정하기 위해 모든 페이지를 수정할 필요가 없다.
리액트의 컴포넌트는 어떤 페이지에서도 불러올 수 있다. 예를 들어 A 페이지에서 헤더 컴포넌트가 필요하면 해당 페이지로 불러오면 되고, B 페이지에서 필요하면 똑 같은 방식으로 불러와 사용하면 된다. 필요가 없어지면 삭제한다.
업데이트란 웹 페이지의 정보를 교체하는 일 (좋아요 기능)이다.
웹에서 페이지를 업데이트하려면 문서 객체 모델(Document Object Model, DOM)을 조작해야 한다. 돔은 HTML 코드를 트리 형태로 변환한 구성물이다. 돔은 웹 브라우저가 직접 생성하며, HTML 코드를 렌더링하기 위해 만든다.
( 렌더링(Rendering)이란 브라우저가 웹의 3가지 언어 HTML, CSS, 자바스크립트를 해석해 페이지의 요소를 실제로 그려내는 과정 )
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script>
function onClickButton() {
const h1Elm = document.getElementById("h1");
h1Elm.innerText = "반가워요!"; ③
}
</script>
</head>
<body>
<h1 id="h1">안녕하세요!</h1>
<button onclick="onClickButton()">인사말 바꾸기</button>
</body>
</html>
- 돔구조에서는 원하는 요소를 찾고 수정 사항을 반영하는 일은 간단하지 않다. 그래서 리액트는 사용자의 특정 행동(예를 들어 좋아요 버튼 클릭)이 일어나거나 데이터가 바뀌어 (업데이트가 필요하면), 어떤 요소를 어떻게 업데이트할지 고민하지 않고, 교체가 필요한 요소는 삭제하고, 새롭게 수정 사항을 반영한 요소를 다시 만들어 통째로 업데이트한다.
1- (3) 빠른 업데이트
빠른 업데이트는 웹 서비스의 성능을 좌우하는 중요한 요소이다. 만약 사용자가 버튼을 클릭할 때마다 페이지를 업데이트하는 데 너무 오래걸린다면 이는 웹서비스 성능의 치명적인 단점이 된다.
< 브라우저는 어떻게 페이지를 표시할까?>
업데이트는 결국 브라우저가 페이지를 다시 렌더링하는 행위이다.
< 랜더링 과정 >
a. HTML 코드를 해석해 돔으로 변환합니다. 마찬가지로 CSS 코드도 해석해 스타일 규칙(Style Rules)으로 변환.
b. 페이지에 어떤 요소가 있고, 어디에 있는지를 아는 돔과 돔 각각의 요소에 스타일을 정의하는 스타일 규칙을 합쳐 렌더 트리(Render Tree)를 만듦.
c. 렌더 트리 정보를 바탕으로 요소의 위치를 픽셀(px) 단위로 계산하는 레이아웃과정을 거침.
(레이아웃은 렌더링 과정에서 가장 많은 연산을 요구하는 작업입니다. )
d. 레이아웃 작업을 거치면 해당 정보를 바탕으로 요소를 실제로 페이지에 그리는 페인팅.
( 페인팅 역시 레이아웃과 더불어 렌더링 과정에서 가장 많은 연산을 요구하는 작업입니다. )
=> 렌더링 과정에서 요소의 위치를 결정하는 레이아웃과 요소를 실제로 페이지에 표시하는 페인팅 과정은 많은 연산을 동반 된다. 따라서 돔의 업데이트가 필요 이상으로 많아지면 브라우저의 성능을 떨어뜨리게 되고 브라우저의 성능이 떨어지면 소위 랙(lag) 현상이 일어나거나 심할 경우 응답 불능 상태에 빠진다.
< 버추얼 돔을 이용한 효율적인 업데이트 >
버추얼 돔(Virtual DOM): 페인팅과 레이아웃을 여러 번 수행하지 않으려면 여러 번의 업데이트를 모았다가 업데이트가 필요할 때 한 번에 처리하여 효율적이다.
- 버추얼 돔을 3번 변경할 동안 실제 돔에는 아무런 변화가 없다가 변경 사항이 모두 종료되면, 변경 사항을 모았다가 한 번에 실제 돔을 업데이트한다. 결과적으로 리액트에서는 여러 번의 업데이트를 모아 한 번에 수행 하므로, 업데이트가 잦아도 브라우저의 성능을 떨어뜨리지 않는다.
리액트 앱은 간단하게 리액트로 만든 웹 서비스이다. 리액트 웹이 아닌 앱으로 부르는 까닭은 리액트로 만든 웹 서비스는 마치 애플리케이션처럼 다양한 상호작용을 제공하기 때문이다. 다시 말해 페이스북의 채팅 또는 좋아요 버튼 같은 서비스는 마치 사용자와 실시간으로 상호작용하는 응용 프로그램(Application, App)과 흡사하기 때문이다.
2- (1) Create React App으로 리액트 앱 만들기
Create React App이라는 Node.js 라이브러리를 이용하여 복잡한 설정없이 리액트 앱을 만들 수 있다. Create React App처럼 복잡한 설정 없이 쉽게 프로젝트를 생성하도록 돕는 개발 도구를 "보일러 플레이트"이라고 하고 보일러 플레이트란 ‘보일러를 찍어내는 틀’이라는 의미로 처음 보일러를 만들 때처럼 복잡한 구조를 염두에 두지 않고도 쉽게 보일러를 만들 수 있다.
a. 문서(Documents) 폴더 아래에 chapter4 폴더를 생성한 다음 비주얼 스튜디오 코드에서 이 폴더를 연다.
b. Create React App으로 리액트 앱을 생성한다.
npx create-react-app . //점(.)은 현재 폴더를 의미합니다.
//( npx(Node Package Execute): ‘노드 패키지 실행’ 을 의미)
/*만약 이때 Need to install the following packages: ...라 는 메시지가 나오면
y를 입력 하면 됩니다.*/
2- (2) 리액트 앱의 구성 요소 살펴보기
Create React App으로 생성한 리액트앱 또한 Node.js 패키지이기 때문에 package.json, package-lock.json, node_modules 같은 Node.js 패키지 구성 파일이 존재한다. 그 외 public, src와 같은 폴더는 Create React App이 자동으로 생성한 폴더들로 리액트 앱을 생성함과 동시에 앱이 동작하는 데 필요한 파일과 폴더를 자동으로 생성한다. 이런 파일과 폴더 모음을 다른 말로 ‘템플릿(Template)’이라고 한다.
- package.json에서 ‘dependencies’ 항목을 보면 Create React App으로 생성한 리액트 앱에는 어떤 라이브러리가 설치되었는지, 리액트 버전은 몇 버전인지 등에 대한 정보가 들어 있음을 알 수 있다.
- public 폴더는 리액트에서 공통으로 사용하는 폰트 파일, 이미지 파일 등을 저장하는 폴더이다.
favicon.ico, index.html, logo192.png, logo512.png, manifest.json, robots.txt 등의 파일들이 포함되어 있다.
- src 폴더는 소스(source) 폴더라는 뜻으로 프로그래밍 소스를 저장하는 폴더이다. 이 폴더는 리액트를 사용하는 동안 자바스크립트 파일들을 한데 모아놓는 곳으로, 프로젝트에서 사용할 소스 파일을 저장한다.
2- (3) 리액트 앱 실행하기
< 실행 >
리액트 앱을 실행하는 명령어는 package.json의 scripts에 작성되어 있다.
//터미널에서 실행
npm run start
이 스크립트를 실행하면 리액트 앱을 실행한다. 자동으로 크롬 웹 브라우저에서 새 탭이 열리면서 리액트 앱의 주소인 http://localhost:3000에 접속한다.
< 종료 >
터미널에서 키보드 <Ctrl>+<C>를 누르면 일괄 작업을 끝내시겠습니까 (Y/ N)?라는 메시지가 출력되면, 현재 작업(리액트 앱)을 종료할 것인지 묻는데, 이때 y를 입력하면 종료된다. 리액트 앱을 종료하면 터미널에서 프롬프트가 다시 활성화된다. 리액트 앱을 종료하면, 브라우저에서 주소 http://localhost:3000과의 접속 또한 자동으로 종료된다. 따라서 브라우저에서 새로고침하면 사이트에 연결할 수 없음 페이지가 나온다.
3 - (1) 리액트 앱에서 어떻게 접속하는 걸까?
a. create-react-app으로 리액트 앱을 만들고
b. npm run start 명령으로 앱을 구동
c. 그 결과 리액트 앱을 실행하면 http://localhost:3000으로 접속한다
Create React App으로 만든 리액트 앱에는 웹 서버가 내장되어 있다. 즉, npm run start 명령을 실행하면 브라우저가 리액트 앱에 접속하도록 앱에 내장된 웹 서버가 동작한다. 결국 내장된 웹 서버 주소로 브라우저가 자동으로 접속한다.
< localhost:3000이라는 주소의 의미 >
localhost는 내 컴퓨터의 주소를 가리킨다. 따라서 localhost 주소로 무언가를 요청하면, 해당 요청은 여러분의 컴퓨터에 전달된다.
3000은 포트(port) 번호로 포트 번호는 컴퓨터에서 실행되고 있는 서버를 구분하는 번호다. 컴퓨터에는 기본적으로는 하나의 주소가 있는데, 이 주소로 요청을 받는다. 그런데 컴퓨터에 여러 개의 서버가 실행되고 있다면, 요청을 받았을 때 어떤 서버에 대한 요청인지 모호하다. 따라서 서버별로 포트 번호를 정해놓으면, 해당 포트 번호에 대한 요청이 들어올 때만 응답하는 식으로 작업을 선별해 처리할 수 있다.
Create React App으로 만든 리액트 앱의 기본 포트 번호는 3000번이다. 따라서 http://localhost:3000과 같이 localhost 3000번 포트의 서버로 접속을 요청해야 정상 적으로 리액트 앱에 접속할 수 있다.
결국 npm run start 명령으로 리액트 앱을 실행하면 내장된 웹 서버가 실행되면서 http://localhost:3000 주소 로 접속하게 된다.
3- (2) 리액트 앱의 동작 원리 상세 보기
http://localhost:3000 주소로 접속하면 이 페이지 하단에는 “Edit src/App.js and save to reload”라는 문장이 있는데, src 폴더의 App.js를 수정하고 저장하여 다시 로드하라는 뜻이다. 비주얼 스튜디오 코드에서 src 폴더의 App.js를 열어보면 App 함수가 작성되어 있고,. 이 함수의 return 문을 다음과 같이 수정하고 <Ctrl>+<S>로 저장한다.
//src/App.is
import logo from "./logo.svg";
import "./App.css";
function App() {
return (
<div className="App">
<h2>안녕하세요</h2>
</div>
);
}
export default App;
< 렌더링 결과가 달라진 이유는 무엇일까요? >
사용자가 리액트 앱에 대한 서비스를 요청하면, 리액트 앱 서버는 public 폴더의 index.html을 보낸다. 일반적으로 특정 웹 서비스에 접속하면 처음 만나는 페이지는 대체로 index.html 파일이다. 따라서 index.html을 열어 보면 왜 이런 결과가 나오는지 알 수 있기 때문에 index.html을 열어 실제 페이지를 브라우저에 표시하는 <body> 태그를 확인해보자.
// public/index.html
(...)
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
(...)
(...)
</body>
index.html의 <body> 태그에는 브라우저가 자바스크립트를 실행할 수 없을 때만 나타나는 <noscript> 태그와 id가 root인 빈 <div> 태그밖에 없다. 따라서 index.html에는 페이지에 표시할 만한 요소가 하나도 없다. index.html에는 페이지에 표시할 만한 요소가 하나도 없는데, http://localhost:3000으로 리액트 앱에 접속하면 앞서 App 컴포넌트에서 수정했던 내용을 페이지가 표시된다.
이를 이해하려면 개발자 도구 [Element] 탭에서 <head> 태그에 작성된 <script defer src=".../bundle.js"> 태그를 확인해야 한다. 개발자 도구의 [Element] 탭에서 이 태그를 찾아 클릭한다.
<script>는 리액트 앱에 접속하면 자동으로 index.html에 추가되는 태그다. <script> 태그는 ‘/static/js/’ 경로에 있는 bundle.js라는 자바스크립트 파일을 불러와 실행한다.
bundle.js는 src 폴더에 있는 index.js와 이 파일이 불러온 모듈을 하나로 묶어놓은 파일이다. 결국 이 번들 파일은 index.js가 작성한 코드에 따라 동작한다. 따라서 index.js에 어떤 내용이 있는지 살펴봐야 리액트 앱의 동작을 제대로 이해할 수 있다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
index.js에서는 import 문으로 App.js에 있는 App 컴포넌트를 포함해 여러 개의 모듈을 불러온다.
ReactDOM.createRoot는 인수로 전달된 요소를 리액트 앱의 루트로 만들어 반환하는 메서드다. 여기서 루트란 뿌리라는 뜻이며, 트리 형태의 돔에서 자바스크립트 함수로 작성된 컴포넌트들의 루트 요소를 가르킨다.
ReactDOM.createRoot 메서드에 인수로 document.getElementById('root')를 전달하는데, 이 메서드는 돔에서 id가 ‘root’인 요소를 찾아 반환한다.
이 코드의 의미를 다시 정리하면 돔에서 id가 ‘root’인 요소를 루트로 만들어 root 라는 변수에 저장한다. public 폴더의 index.html에 있는 <div> 태그가 바로 루트 요소이다.
변수 root에는 현재 리액트의 루트가 저장되어 있다. render 메서드는 인수로 리액트 컴포넌트를 전달하는데, 이 컴포넌트를 돔 루트에 추가한다. 따라서 render 메서드가 수행되면 전달된 리액트 컴포넌트가 돔에 추가되어 페이지에 나타난다. 결론적으로 이 코드는 App 컴포넌트를 돔 루트에 추가하므로, 페이지에 App 컴포넌트에서 정의한 HTML 요소가 표시된다.
a. localhost:3000으로 접속을 요청하면 public 폴더의 Index.html을 반환한다.
b. index.html에는 src 폴더의 index.js와 해당 파일이 가져오는 자바스크립트 파일을 한데 묶어 놓은 bundle.js를 불러온다. <script> 태그에서 자동으로 추가한다.
c. bundle.js가 실행되어 index.js에서 작성한 코드가 실행된다.
d. Index.js는 ReactDOM.createRoot 메서드로 돔에서 리액트 앱의 루트가 될 요소를 지정한다.
e. render 메서드를 사용해 돔의 루트 아래에 자식 컴포넌트를 추가한다. 결과적으로 App 컴포넌트가 렌더링된다.
출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023).
Corner React.js1
Editor: Marin
[React.js 1팀] 5장 리액트의 기본 기능 다루기 (1) (1) | 2024.11.08 |
---|---|
[React.js 1팀] 03장. Node.js (0) | 2024.10.11 |
[React.js 1팀] 2장. 자바스크립트 실전 - 구조 분해 할당 ~ 비동기 처리 (0) | 2024.10.04 |
[React.js 1팀] 1장. 자바스크립트의 기초 ~ 2장. 자바스크립트 실전 - 반복문 응용하기 (4) | 2024.09.27 |