Post

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 객체를 다루어야하는 재앙이 벌어진다는 말이다.

이래서 다양한 프레임워크나 라이브러리가 등장했다 이말이다.

This post is licensed under CC BY 4.0 by the author.