HTML, CSS, JS만 사용해도 웹을 충분히 만들 수 있지만 React 혹은 Vue, Angular 등과 같은 라이브러리(프레임워크)를 사용하는 이유는 다양합니다.
리액트의 경우 페이지 렌더 시 필요한 부분만 업데이트할 수 있는 앱처럼 사용자 경험이 뛰어난 이유도 있겠지만 개발자의 입장에서 봤을 때 기능 개발에만 집중할 수 있다는 이유가 가장 큰 이유라고 생각합니다.
단점으로는 seo가 취약하다는 부분이 있습니다.
리액트 웹의 구조는 js로 만들어지기에 index.html 하나뿐 아니라 안에 내용도 없기에 당연하게도 검색에 취약하다는 단점이 있습니다.
개발자의 입장에서 바라보는 리액트는 싱글 페이지 애플리케이션의 UI를 생성하는데 집중한 라이브러리입니다.
자바스크립트에 HTML을 포함하는 JSX(Javascript XML)라는 문법과 단반향 데이터 바인딩(One Way Data Binding)을 사용하며, 가상 돔(Virtual DOM)이라는 개념을 사용해 웹 애플리케이션의 퍼포먼스를 최적화한 라이브러리입니다.
다만 리액트는 싱글 페이지 애플리케이션에서 UI를 만드는 라이브러리이기에 뷰/앵글러와 같은 프레임워크에 비해 페이지 전환 기능을 제공하지 않아 react-router-dom과 같은 추가적인 라이브러리를 사용해야 하는 부족한 부분도 존재합니다.
가상 돔을 이해하기 위해선 HTML과 CSS의 렌더링 원리를 이해해야합니다.
간단하게 원리를 이야기하자면 웹 브라우저와 서버의 통신으로 html과 css를 전달 받으면 각각을 파싱하여 돔 노드로 이루어진 html의 DOM tree 그리고 css의 CSSOM tree를 생성합니다.
이렇게 만들어진 트리들은 파싱 과정 중 하나인 렉싱(Lexing)이라는 과정을 통해 객체로 변환하여 attach라는 함수를 가지고 있는 node를 생성합니다.
그리고 Attachment(각각의 브라우저별로 렌더 트리를 만드는) 과정에서 노드들이 가지고 있는 attach 메소드들을 호출하는데, 해당 메소드가 스타일 정보를 계산하여 결과값을 객체 형태로 반환하여 스타일 정보를 계산하게 됩니다.
중요한 것은 이러한 과정이 모두 동기적(Synchronous)으로 동작한다는 것입니다.
만약 렌더 트리에 새로운 노드가 추가되면 해당 노드가 가지고 있는 attach 메소드를 실행하여 계산하는 과정이 필요합니다.
또한 Attachment 과정을 거친 후에 레이아웃 과정을 거쳐 뷰포트 내에서 각 노드들의 정확한 위치와 크기를 계산합니다.
그리고 레이아웃이 완료된 요소들의 위치 및 크기 그리고 스타일 계산이 완료된 렌더 트리를 사용해 브라우저는 각 노드 요소들을 실체 화면에 그리는 페인팅이라는 과정을 진행합니다.
이러한 과정을 거쳐 렌더링이 완료되어도 자바스크립트를 사용하여 DOM 조작을 하게 되면 각 노드의 좌표를 계산하기 위해 레이아웃 과정이 다시 실행되고 실제 화면에 그리기 위해 페인팅 과정 또한 다시 일어나게 됩니다.
이를 리플로우(Reflow)와 리페인트(Repaint)라고 하는데 당연히 DOM의 각 노드에 대한 계산 과정이 다시 필요하기 때문에 웹의 성능이 떨어질 수 밖에 없습니다.
리액트는 이러한 리플로우와 리페인트에의해 떨어진 웹 성능을 실제 돔과 동일한 가상 돔을 메모리상에 만들고 특정 이벤트에 의해 돔 조작이 발생하면 메모리상에 만들어둔 가상 돔에서 모든 과정을 수행한 후에, 실제 돔을 갱신하여 리플로우와 리페인트를 최소화했습니다.
게시판을 예시로 들자면 게시글을 작성을 작성하고 게시글을 등록하고 등록된 게시글의 리스트를 업데이트 해야하는 과정이 필요합니다.
만약 가상 돔이 없다면 게시글을 작성하고 등록하는 과정에서 한번 게시글의 리스트를 업데이트 하기위해 또 다시 한번 리플로우와 리페인트가 발생하게 됩니다.
하지만 리액트의 경우 이러한 모든 과정을 가상 돔에서 수행합니다.
게시글을 작성해서 등록하고 게시글의 리스트를 업데이트를 가상 돔에서 계산한 후에 계산 결과를 브라우저에 전달하여 리플로우와 리페인트를 한 번만 수행할 수 있도록 합니다.
자바스크립트와 html을 동시에 사용할 수 있는 리액트의 독특한 문법으로 html에 자바스크립트의 변수들을 사용할 수 있는 일종의 템플릿 언어입니다.
개발자의 입장에서 자바스크립트에서 html을 작성하고 자바스크립트의 유요한 모든 표현식등을 넣을 수 있기에 보다 편하게 코드를 작성할 수 있습니다.
또한 선언형 프로그래밍을 활용하여 가독성 높은 코드를 작성할 수 있습니다.
<ul id=”list”></ul>
<script>
const arr = [1, 2, 3, 4, 5]
const elem = document.querySelector("#list");
for(let i = 0; i < arr.length; i ++) {
let li = document.createElement("li");
li.innerHTML = arr[i];
elem.appendChild(li);
}
</script>
위 코드와 아래 코드는 동일한 결과를 만들어내지만 아래 코드가 가독성 뿐만아니라 사용함에 있어서도 뛰어난걸 알 수 있습니다.
const arr = [1, 2, 3, 4, 5];
return (
<ul>
{arr.map((elem) => (
<li>{elem}</li>
))}
</ul>
);
JSX 문법은 반드시 부모 요소 하나가 모든 요소를 감싸는 형태여야 하는데 이는 위에서 말한 가상 돔이 컴포넌트의 변화를 감지할 때 효율적으로 비교할 수 있도록 컴포넌트 내부는 하나의 돔 트리 구조로 이루어져야 한다는 규칙이 있습니다.
데이터 바인딩 이란 두 데이터 혹은 정보의 소스를 모두 일치시키는 기법이다. 즉 화면에 보이는 데이터와 브라우저 메모리에 있는 데이터를 일치시키는 기법이다.
먼저 양방향 데이터 바인딩은 사용자 UI의 데이터 변경을 감시하는 Watcher와 자바스크립트 데이터의 변경을 감시하는 Watcher가 UI와 자바스크립트 데이터를 자동으로 동기화 시켜주는 방식을 말합니다.
덕분에 사용자가 입력된 값이나 변경된 값에 따라 내용이 바로 변경되기 때문에 따로 체크할 필요가 없어 코드의 사용면에서 코드량을 크게 줄여줄 수 있지만 하나의 데이터 동기화에 두 개의 Watcher가 사용되고, 데이터가 많아지면 해당 데이터의 동기화를 위한 많은 Watcher가 생성되기 때문에 성능이 감소될 수 있습니다.
단반향 데이터 바인딩은 단 하나의 Watcher가 자바스크립트의 데이터 갱신을 감지해서 사용자의 UI 데이터를 갱신합니다.
다만 사용자가 UI를 통해 자바스크립트의 데이터를 갱신할 때는 이벤트를 통해 갱신하게 됩니다.
이처럼 단반향 데이터 바인딩은 하나의 Watcher를 사용하기 때문에 양방향 데이터 바인딩이 가지고 있는 성능적인 이슈를 해결하고 데이터 흐름이 부모 -> 하위 컴포넌트이기 때문에 코드를 이해하기 쉽고 데이터 추적 및 디버깅이 쉽습니다.
리액트의 장점 중 하나인 컴포넌트라고 부르는 고립된 코드를 사용하여 UI를 구성할 수 있는데
이러한 컴포넌트는 재사용을 통해 개발의 생산성을 향상시킬 수 있습니다.
class 형태의 리액트 컴포넌트에서만 사용할 수 있었던 state 및 lifecycle등을 함수 형태의 컴포넌트에서도 사용할 수 있게 만들어주는 기능
functional component 그리고 class component 가 있지만 hook이 나온 시점부터 class를 사용하지 않습니다. 정확하게는 사용할 수 있고 사용해도 되지만 사용할 필요가 없습니다.
이유는 단순합니다. 더 편하니까
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
react 공식문서에서 가져온 위 코드와 아래 코드의 비교로 쉽게 이해가능하다
import React, { useState } from 'react';
function Example() {
// "count"라는 새 상태 변수를 선언합니다
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
class를 이용해서 만들어진 코드들은 js에서 this의 작동 방법을 알아야만 하며, 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기가 어려웠습니다.
거기다 Life cycle 은 컴포넌트가 브라우저에 보일 때, 업데이트될 때, 사라질 때 각 단계 전, 후로 특정 메서드가 호출되는데 크게 어려운 건 아니지만 한 번 더 생각하고 코드를 작성해야 하는 불편함이 있는 건 사실입니다.
그렇다고 이전의 class를 이용해서 만들어둔 코드들을 다시 작성할 필요는 없습니다.
이전 버전과의 호환성 문제가 없다고 공식문서에 나와 있기 때문입니다.
라이브러리, 프레임워크들 중에 가장 많은 유저들이 리액트를 사용하는데 단지 사용자가 많다는 이유보단, 많은 개발자가 사용하는 만큼 생태계가 크게 활성화 되어 있기 때문에 정보 및 소스를 구하기가 비교적 쉽습니다.
공식문서가 빈약하다고 이야기가 나오긴 하지만 개인적으로 번역과 친절한 설명 때문에 사용하기도 합니다.