Ray Book
프레임워크의 철학

왜 프레임워크가 필요했는가

jQuery의 한계에서 선언적 UI의 등장까지, React, Vue, Angular, Svelte가 같은 시대에 다른 답을 내놓은 이유

frameworkreactvueangularsveltehistory

왜 우리는 프레임워크를 쓰는가?

대부분의 프론트엔드 개발자는 React나 Vue 같은 프레임워크부터 배웁니다. 그래서 "왜 프레임워크를 쓰는가?"라는 질문에 대한 답을 한 번도 진지하게 생각해본 적이 없는 경우가 많습니다. "원래 쓰니까" 정도로 넘어가죠.

하지만 프레임워크 없이 웹 애플리케이션을 만들어 보면, 그 이유를 몸으로 알게 됩니다. 간단한 할 일 목록 하나만 만들어 봅시다.

jQuery 시대의 고통

2006년에 등장한 jQuery는 혁명이었습니다. 브라우저마다 다른 DOM API를 통일하고, $ 하나로 간결하게 DOM을 조작할 수 있었습니다. 하지만 애플리케이션이 복잡해지면 문제가 드러납니다.

할 일 목록을 jQuery로 만들어 보겠습니다.

// jQuery, 명령형 DOM 조작
const todos = [];

$('#add-btn').click(() => {
  const text = $('#input').val();
  todos.push({ text, done: false });
  // DOM을 직접 업데이트해야 한다
  $('#list').append(`<li>${text} <button class="del">삭제</button></li>`);
  $('#count').text(todos.length);
  $('#input').val('');
});

// 삭제도 직접...
$(document).on('click', '.del', function () {
  const idx = $(this).parent().index();
  todos.splice(idx, 1);
  $(this).parent().remove();
  $('#count').text(todos.length);
});

동작은 합니다. 하지만 이 짧은 코드에서 이미 세 가지 문제가 보입니다.

  1. 상태와 DOM이 따로 논다 , todos 배열과 실제 화면의 <li> 목록이 각각 존재합니다. 둘 중 하나만 어긋나도 버그입니다. 필터링이나 정렬 기능을 추가하면? 배열은 바뀌었는데 DOM은 그대로인 상황이 금방 발생합니다.

  2. DOM 조작 코드가 비즈니스 로직과 뒤섞인다 , "할 일을 추가한다"는 로직과 "<li>를 만들어서 #list에 붙인다"는 UI 코드가 같은 함수 안에 있습니다. 어떤 것이 비즈니스 로직이고 어떤 것이 표현 로직인지 구분이 안 됩니다.

  3. 추적이 불가능해진다 , 앱이 커지면 어떤 코드가 어떤 DOM 요소를 바꾸는지 알 수 없습니다. 버그가 발생했을 때 "이 <li>를 누가 건드렸지?"를 찾으려면 전체 코드를 뒤져야 합니다.

핵심 문제는 하나입니다. 상태(데이터)와 뷰(DOM)의 동기화를 개발자가 수동으로 해야 한다는 것. 앱이 작을 때는 괜찮지만, 수십 개의 상태와 수백 개의 DOM 요소가 얽히면 더 이상 사람이 관리할 수 있는 수준이 아닙니다.

선언적 UI의 아이디어

택시를 탈 때를 생각해 봅시다.

  • 명령형 : "다음 교차로에서 좌회전하고, 200미터 직진한 다음, 사거리에서 우회전하세요"
  • 선언적 : "강남역으로 가주세요"

명령형은 과정을 하나하나 지시합니다. 선언적은 결과 만 알려주고 과정은 맡깁니다.

jQuery는 명령형입니다. "이 요소를 찾아서, 텍스트를 바꾸고, 클래스를 추가하고, 자식을 제거해." 개발자가 DOM을 어떻게 바꿀지 한 단계씩 지시해야 합니다.

모든 모던 프레임워크는 같은 아이디어에서 출발합니다.

"상태만 바꾸면 UI가 알아서 바뀌면 좋겠다."

상태가 이러하면 UI는 이렇게 보여야 한다, 이 선언 만 해두면, 상태가 바뀔 때 프레임워크가 DOM 업데이트를 알아서 처리합니다. 개발자는 "무엇을 보여줄 것인가"에만 집중하고, "어떻게 DOM을 조작할 것인가"는 프레임워크에 위임하는 것입니다.

이 단순한 아이디어가 2010년대 프론트엔드를 완전히 바꿔놓았습니다.

타임라인

같은 문제를 풀기 위해, 2010년부터 2020년까지 여러 프레임워크가 등장했습니다. 각각이 어떤 시점에, 어떤 슬로건으로 등장했는지 살펴보세요.

2006jQuery1 / 5
  1. 2006jQueryWrite less, do more
  2. 2010Angular.js양방향 바인딩의 시작
  3. 2013ReactUI를 함수로 표현하라
  4. 2014Vue점진적으로 채택하라
  5. 2016Svelte런타임을 없애라
DOM 조작을 간편하게 만들었지만, 앱이 커지면 상태와 UI의 동기화가 고통이 됩니다.

4가지 답, 같은 문제, 다른 철학

네 프레임워크 모두 "상태와 UI의 동기화"라는 같은 문제를 풀었습니다. 하지만 접근 방식이 완전히 다릅니다.

Angular (2010)

Google이 만들었습니다. Angular의 철학은 "HTML을 확장하자" 입니다.

HTML은 원래 정적인 문서 마크업 언어입니다. Angular는 여기에 *ngFor, *ngIf, [(ngModel)] 같은 디렉티브를 추가해서 HTML이 동적으로 동작하도록 만들었습니다. 초기 Angular.js의 양방향 바인딩은 혁신적이었습니다, 입력 필드에 타이핑하면 모델이 바뀌고, 모델이 바뀌면 UI가 자동으로 업데이트되었으니까요. "상태-DOM 동기화를 직접 해야 한다"는 jQuery 시대의 고통이 사라진 것입니다.

Angular는 풀스택 프레임워크 를 지향합니다. 라우팅, HTTP 클라이언트, 폼 관리, 의존성 주입(DI)까지 프레임워크 자체에서 제공합니다. 선택지를 줄이는 대신, "Angular 방식"으로 통일된 구조를 강제합니다. 대규모 팀에서 일관된 아키텍처가 필요할 때 강점을 발휘합니다.

React (2013)

Facebook (현 Meta) 이 만들었습니다. React의 핵심 철학은 한 줄로 요약됩니다.

UI = f(state)

UI는 상태의 함수 입니다. 상태가 바뀌면 함수를 다시 호출하고, 이전 결과와 새 결과를 비교해서 차이만 실제 DOM에 적용합니다. 이것이 Virtual DOM의 핵심 아이디어입니다.

React는 스스로를 "라이브러리"라고 부릅니다. 뷰 레이어만 담당하고, 라우팅이나 상태 관리 같은 나머지는 생태계에 맡깁니다. 이 전략은 거대한 서드파티 생태계를 만들었습니다, React Router, Redux, Next.js 등이 모두 이 생태계의 산물입니다.

Vue (2014)

전 Google 엔지니어 Evan You가 개인 프로젝트로 만들었습니다. Vue의 철학은 "점진적으로 채택하자" 입니다.

Angular의 템플릿 직관성과 React의 컴포넌트 모델, 양쪽의 장점을 취했습니다. CDN에서 스크립트 하나를 로드해서 기존 페이지에 부분적으로 적용할 수도 있고, Vue CLI로 전체 SPA를 구축할 수도 있습니다. 작게 시작해서 필요한 만큼 확장할 수 있다는 것이 핵심입니다.

"쉬운 진입 장벽"을 중요하게 여깁니다. HTML 템플릿, CSS, JavaScript를 단일 파일 컴포넌트 (.vue) 안에 함께 두는 구조는 웹 기초를 아는 사람이라면 누구나 바로 이해할 수 있습니다.

Svelte (2016)

New York Times 개발자였던 Rich Harris가 만들었습니다. Svelte의 철학은 가장 급진적입니다.

"프레임워크를 없애자."

React, Vue, Angular는 런타임에 프레임워크 코드가 브라우저에서 실행됩니다. Virtual DOM 비교 로직이든, 반응성 시스템이든, 브라우저가 매번 처리해야 할 코드가 존재합니다. Svelte는 이 접근을 뒤집었습니다, 컴파일 타임 에 컴포넌트를 분석해서, 최적화된 순수 JavaScript를 생성합니다.

결과적으로 Svelte 앱에는 "프레임워크 런타임"이 없습니다. 빌드된 결과물은 DOM을 직접 조작하는 효율적인 vanilla JS입니다. Svelte는 프레임워크가 아니라 컴파일러 입니다.

같은 카운터, 4가지 방법

철학의 차이는 코드에서 드러납니다. 가장 단순한 카운터 앱을 네 프레임워크로 만들어 보겠습니다.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      클릭: {count}
    </button>
  );
}

네 가지 코드가 하는 일은 완전히 같습니다 . 상태(count)를 선언하고, 버튼 클릭 시 1을 증가시키고, 변경된 값을 UI에 반영합니다.

차이는 "어떻게 표현하는가"뿐입니다.

  • React , useState 훅으로 상태를 선언하고, setter 함수로 업데이트합니다. JSX로 UI를 작성합니다.
  • Vue , ref()로 반응형 상태를 만들고, 템플릿에서 {{ }} 로 바인딩합니다. @click은 Vue의 이벤트 디렉티브입니다.
  • Angular , 클래스 프로퍼티가 곧 상태이고, 템플릿에서 (click) 으로 이벤트를 바인딩합니다. 데코레이터 (@Component) 로 메타데이터를 정의합니다.
  • Svelte , $state() rune으로 상태를 선언하면, 일반 변수처럼 count++로 업데이트할 수 있습니다. 가장 적은 보일러플레이트로 같은 결과를 냅니다.

표현 방식은 다르지만, 핵심 패턴은 동일합니다: 상태 선언 → 이벤트 처리 → UI 자동 반영. jQuery 시대에 수동으로 해야 했던 "상태-DOM 동기화"를 프레임워크가 대신해주는 것입니다.

프레임워크는 도구다

프레임워크 전쟁은 무의미합니다. React가 더 좋은가, Vue가 더 쉬운가, 이런 논쟁은 "망치가 좋은가, 드라이버가 좋은가"를 따지는 것과 비슷합니다.

네 프레임워크 모두 같은 문제(상태와 UI의 동기화) 를 다른 방식으로 풀었을 뿐입니다. Angular는 완전한 플랫폼을 제공하고, React는 최소한의 뷰 레이어만 담당하고, Vue는 점진적 채택을 가능하게 하고, Svelte는 런타임을 제거했습니다.

이 시리즈에서는 "어떤 프레임워크가 좋은가"가 아니라, "어떤 문제를 어떻게 풀었는가" 를 살펴봅니다. 각 프레임워크의 내부 메커니즘을 이해하면, 어떤 프레임워크를 쓰든 더 나은 코드를 작성할 수 있습니다.


다음 글에서는 반응성 (Reactivity) 을 다룹니다. 버튼을 클릭해서 count를 바꿨을 때, 각 프레임워크가 어떻게 이 변경을 감지하고 DOM을 업데이트하는지, 그 내부 메커니즘을 살펴보겠습니다.