24/05/16 React 가 등장한 이유
목적
리액트 관련 강의도 듣게 되면서 프레임워크가 왜 필요하게 되었는지 간단히 정리해보려고 한다. Single-Page Application 과 Server-Side Rendering, Client-Side Rendering 등에 대해서 작성해보자.
Single-Page Application(SPA) 의 등장
SPA 등장 이전의 웹 사이트 환경은 서비스를 제공하는 서버에서 화면을 구성하는데 필요한 데이터나, 웹 문서 등을 모두 서버에서 조합해서 클라이언트에게 보냈다. 이를 Server-Side Rendering 이라고 부른다. 웹 문서를 구성하는 역할이 전적으로 서버에게 있고, 클라이언트는 브라우저를 통해, 서버에서 받아온 웹 문서를 표시해줄 뿐이었다.
그러나, 웹이 더욱 발전하면서 다양한 기능을 제공하고 복잡한 구조를 갖게되었다. 이 지점부터는 Web Application 이라고 부르게 되었다. 그 기반에는 SPA 가 있었다. 더불어 함께 등장한 방식이 Client-Side Rendering 이다.
Client-Side Rendering은 말 그대로, 웹 문서의 구성을 클라이언트에서 진행하는 것이다. 서버에서는 클라이언트가 요청한 데이터만 보내주어 유저가 서비스를 원활하게 이용할 수 있게 해준다.
인스타그램을 사용할 때를 떠올려 보자.
홈 화면에 있다가, 돋보기 버튼을 눌러 화면 전환을 하더라도 앱 자체가 새로 고침을 하지는 않는다. 또는 게시글에 작성된 댓글 목록을 보다가, 댓글 목록을 새로 불러오더라도 앱 전체가 새로 고침이 되지 않는다.
이런 것을 SPA라고 생각하면 된다.
Vanilla JS vs React
근데, SPA 를 구현할 때 왜 JS 는 안 쓰는 것일까?
내가 직접 경험해본 적은 없지만, 에어비엔비 어플을 떠올려보자.
원하는 지역의 숙소를 찾기 위해서, 다양한 조건을 통해 검색하려고 할 것이다.
반드시 독채여야만 한다든지.. 바베큐가 가능해야 한다든지.. 몇 명 이상은 수용이 가능해야만 한다든지… 그외에도 원하는 조건이 분명 존재할 것이다.
이때, 유저가 선택하는 다양한 옵션을 바닐라 JS 로 관리하기에는 문제가 많았다는 것이다.
바닐라 JS 로 웹 사이트의 변화를 감지하는 방법은 Dom 요소에 직접 접근해서 값을 비교하는 수 밖에 없는데, 아래 에어비엔비 예시를 보자.
단순히 여행지 부터, 체크인-체크아웃 날짜 등 다양한 옵션이 존재한다.
이걸 하나하나 DOM 요소가 변했는지 아닌지 확인해야하고, 그에 따라 계속해서 정보를 바꿔주어야한다.
그리고, DOM 으로 접근해 값을 가져오는 것도 결국 HTML 문서에 그 값이 저장되는 것이니 추적이 어려울 수 밖에 없는 것이다.
추적이 어렵다 보니, 유저가 옵션을 선택했을 때 바로바로 UI 에서 변화가 일어나지 않으니 이를 개선하기 위해 프레임워크나 라이브러리가 등장했다.
스스로 이해하고자 꽤나 길게 적어보았다!
간단 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { useEffect, useState } from "react";
export default function App() {
const [advice, setAdvice] = useState("");
const [count, setCount] = useState(0);
async function getAdvice() {
const res = await fetch("https://api.adviceslip.com/advice");
const data = await res.json();
setAdvice(data.slip.advice);
setCount((c) => c + 1);
}
useEffect(function () {
getAdvice();
}, []);
return (
<div>
<h1>{advice}</h1>
<button onClick={getAdvice}>Get advice</button>
<Message count={count} />
</div>
);
}
function Message(props) {
return (
<p>
You have read <strong>{props.count}</strong> pieces of advice
</p>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Vanilla JS Advice</title>
</head>
<body>
<h1 class="advice"></h1>
<button class="btn">Get advice</button>
<p>You have read <strong class="count"></strong> pieces of advice</p>
<script>
// Manually selecting DOM elements (which require a class or ID in markup)
const adviceEl = document.querySelector(".advice");
const btnEl = document.querySelector(".btn");
const countEl = document.querySelector(".count");
const getAdvice = async function () {
const res = await fetch("https://api.adviceslip.com/advice");
const data = await res.json();
// Updating values
advice = data.slip.advice;
count = count + 1;
// Manually updating DOM elements
countEl.textContent = count;
adviceEl.textContent = advice;
};
// Setting initial values
let count = 0;
let advice;
getAdvice();
// Attaching an event listener
btnEl.addEventListener("click", getAdvice);
</script>
</body>
</html>
두 코드가 하는 일은 동일하게 API 를 통해 명언 하나를 가져오는 것이다.
먼저 React 로 작성한 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const [advice, setAdvice] = useState("");
const [count, setCount] = useState(0);
async function getAdvice() {
const res = await fetch("https://api.adviceslip.com/advice");
const data = await res.json();
setAdvice(data.slip.advice);
setCount((c) => c + 1);
}
useEffect(function () {
getAdvice();
}, []);
return (
<div>
<h1>{advice}</h1>
<button onClick={getAdvice}>Get advice</button>
<Message count={count} />
</div>
);
이 코드로, Get Advice 버튼을 누를 때 마다 명언을 <h1>
에서 출력할 수 있다. DOM 객체를 선택해 값을 바꿔주는 코드를 작성하는 등의 별도의 조작을 하지 않아도, 새로운 명언을 가지고 오는 것이 감지되면 값이 갱신된다는 것이다.
다음은 Vanilla JS 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
// Manually selecting DOM elements (which require a class or ID in markup)
const adviceEl = document.querySelector(".advice");
const btnEl = document.querySelector(".btn");
const countEl = document.querySelector(".count");
const getAdvice = async function () {
const res = await fetch("https://api.adviceslip.com/advice");
const data = await res.json();
// Updating values
advice = data.slip.advice;
count = count + 1;
// Manually updating DOM elements
countEl.textContent = count;
adviceEl.textContent = advice;
};
// Setting initial values
let count = 0;
let advice;
getAdvice();
// Attaching an event listener
btnEl.addEventListener("click", getAdvice);
</script>
이건 꽤나 친숙한 코드라고 할 수 있다.
기본적으로 event 를 감지할 HTML 요소를 선택하고, api 호출 결과를 저장한다.
이후 textContent 를 변경해 HTML 에 반영한다.
물론 지금은 짧은 코드라 문제가 없지만, 사이즈가 조금만 커진다면 수 십개의 DOM 객체를 다루어야하는 재앙이 벌어진다는 말이다.
이래서 다양한 프레임워크나 라이브러리가 등장했다 이말이다.