프레임워크에 대한 이야기
프레임워크는 필요 없다. 중요한 것은 그림이지 프레임(그림틀)이 아니다.
- 클라우스 킨스키(Klauis Kinski) -
이 책의 저자는 프레임워크 없이 프론트엔드 애플리케이션을 개발하는 방법에 관한 책을 읽어야 하는 이유로 가끔 프레임워크만으로 작업을 수행하기가 충분하지 않기 때문이라고 말하고 있다. 1장에서는 프레임워크에 대한 저자의 생각과 프레임워크 없이 개발하는 방법을 배우는 것이 왜 중요한지에 대한 저자의 믿음으로 시작한다.
프레임워크란?
캠브리지 사전의 프레임워크의 정의는 다음과 같다.
무언가를 만들 수 있는 지지 구조
이 정의는 SW 프레임워크의 일반적인 개념과 일치한다. 앵귤러 애플리케이션의 구조를 생각해 보면 이 정의와 정확히 일치하는 것을 알 수 있다. 앵귤러는 서비스, 구성요소와 파이프 같은 기본 요소를 사용해 애플리케이션을 빌드하는 데 필요한 구조를 제공한다.
실제 애플리케이션의 스택은 다른 요소들을 포함하고 있다. 로대쉬(Loadash)를 사용해 배열이나 객체를 조작하거나 Moment.js를 사용해 날짜를 파싱할 수 있다. 이것이 프레임워크 도구일까? 자바스크립트 커뮤니티는 이를 라이브러리라고 부른다. 그렇다면 라이브러리와 프레임워크의 차이는 무엇일까? 이 책에서는 다음과 같이 정의한다.
프레임워크는 코드를 호출한다. 코드는 라이브러리를 호출한다.
나는 이 정의가 바로 와닿지는 않는다. 그래서 찾아보니 프레임워크위에서 내 코드가 실행되기 때문에 프레임워크가 내 코드를 호출하고 내 코드안에서 라이브러리를 import를 해서 호출해서 사용하기 때문에 코드가 라이브러리를 호출한다는 의미가 된다고 한다. 여기서 내가 호출당하는 입장이 되는 것을 제어의 역전(Inversion Of Control, IOC)라고 하는데 간단히 말해서 내 코드를 등록해두고 호출되기를 기다리는 것이다. 어떤 시기에 호출될지는 프레임워크가 결정한다.

프레임워크와 라이브러리 비교
앵귤러와 Moment.js를 사용해서 프레임워크와 라이브러리의 차이점을 알아본다.
리스트 1-1. 앵귤러 Service 예제
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
const URL = 'http://example.api.com/';
@Injectable({ providedIn : 'root', })
export class PeopleService{
constructor(private http : HttpClient){ }
list(){
return this.http.get(URL);
}
}
리스트 1-2. 앵귤러 Component 예제
import { Component, OnInit } from '@angluar/core';
import { PeopleService } from '../people.service';
@Component(
{
selector : 'people-list',
templateUrl : './people-list.component.html'
})
export class PeopleListComponent implements OnInit{
constructor(private peopleService : PeopleService){}
ngOnInit(){
this.loadList();
}
loadList(): void{
this.peopleService.getHeroes()
.subscribe(people => this.people = people);
}
}
리스트 1-3. Moment.js 예제
import moment 'moment';
const DATE_FORMAT = 'DD/MM/YYYY';
export const formatDate = date => {
return moment(date).format(DATE_FORMAT);
}
앞선 정의를 염두에 두면 앵귤러는 프레임워크고 Moment.js는 라이브러리라는 것을 이해할 수 있다. 앵귤러에서는 PeopleListComponent가 PeopleService와 상호작용하게 하려면 @Injectable 어노테이션을 사용하고 생성자에 인스턴스를 넣어야 한다.
하지만 Moment.js는 애플리케이션 코드를 어떻게 구성해야 하는지에 대해 특별한 형식을 요구하지 않는다. 그저 가져와 사용하기만 하면 된다. 공개 API를 존중하는 한 계속해서 사용할 수 있다.
이 책에서는 리액트는 프레임워크일까? 라이브러리일까? 라는 질문을 던진다. 이 질문에 답하기 위해 프레임워크 방식(Framework way)를 소개하고 있다.
프레임워크 방식
리스트 1-3에서 보듯이 Moment.js는 개발자에게 코드를 강요하지 않지만 앵귤러는 매우 독선적이다. 다음 절에서 몇 가지 제약 조건을 알아본다.
언어
일반 ECMAScript로도 앵귤러 애플리케이션을 작성할 수 있지만, TypeScript가 앵귤러 생태계의 표준이다.
의존성 주입
요소가 앵귤러에서 통신할 수 있게 하려면 유형에 따라 의존성 주입 매케니즘을 사용해 요소를 주입해야 한다고 한다. 이전 버전의 앵귤러JS에는 서비스 로케이터 패턴을 기반으로 하는 의존성 주입 메커니즘이 있었다.
리스트 1-4. 앵귤러JS 의존성 주입
const peopleListComponent = peopleService => {
//실제 코드
}
angular.component( 'people-list', [ 'peopleService', peopleListComponent ]);
옵저버블
앵귤러는 옵저버블을 사용한 반응형 프로그래밍용 라이브러리인 RxJS를 기반으로 설계됐다. PeopleListService에서 데이터를 가져오려면 Observable 객체의 subscribe 메서드를 사용해야 한다. 이 접근 방식은 HTTP 요청이 Promise처럼 설계되는 다른 프론트엔드 프레임워크와 다르다. Promise는 작업의 최종 완료를 나타내는 표준 방법이다. RxJS를 사용하면 옵저버블을 Promise로 또는 반대로 Promise를 옵저버블로 쉽게 변환할 수 있다.
프레임워크의 개발자들이 만든 제약 조건 외에도 다른 제약 조건도 포함된다. 예를 들어 커뮤니티의 사실상 표준은 핵심 표준만큼이나 중요하다. 앵귤러JS 생태계에서 존 파파의 스타일 가이드 가 앵귤러JS 애플리케이션을 작성하는 방식이었다. 이를 꼭 따라 해야 하는 것은 아니지만 웹에서 볼 수 있는 대부분의 코드는 이런 방식으로 작성됐다.
이런 제약 조건이 반드시 좋거나 나쁜 것은 아니다. 하지만 프로젝트에 적합한 도구인지를 평가하려면 팀이 선택한 프레임워크의 '방식'을 분석하는 것은 매우 중요하다.
리액트에 대해 이야기한다.
리액트 홈페이지에서 리액트는 '사용자 인터페이스 구축을 위한 자바스크립트 라이브러리'라고 정의돼 있다. 리액트는 라이브러리다. 하지만 현실은 이보다 훨씬 더 복잡하다. 리액트의 주요 제약 조건은 선언적 패러다임의 사용이다. DOM을 직접 조작하는 대신 구성 요소의 상태를 수정한다. 그러면 리액트가 대신 DOM을 수정한다.
리스트 1-7. 리액트 Pose 애니메이션 예제
import React, { Component } from 'react';
import posed from 'react-pose';
const Box = posed.div({
hidden: { opacity : 0},
visible: { opacitiy : 1},
transition: { ease: 'linear', duration: 500 }
});
class PosedExample extends Component{
constructor(props){
super(props)
this.state = { isVisible: true }
this.toggle = this.toggle.bind(this)
}
toggle(){
this.setState({ isVisible: !this.state.isVisible })
}
render(){
const { isVisible } = this.state const pose = isVisible ? 'visible' : 'hidden'
return (
<div>
<Box className='box' pose={pose} />
<button onClick={this.toggle}>Toggle</button>
</div>
)}
}
export default PosedExample
이 예제는 사각형을 직접 애니메이션하도록 작업하지 않는다. 애니메이션으로 상태를 매핑하는 방법을 선언한 다음, 상태를 변경하면 된다. 이것이 리액트에서 사용되는 선언적 패턴의 핵심이다.
자바스크립트 프레임워크의 연혁
제이쿼리
2006년, 존 레식이 만든 제이쿼리는 모든 자바스크립트 프레임워크의 모체가 됐다. 제이쿼리의 가장 중요한 기능은 var element = $('.my-class') 같은 selector 구문이다.
지금은 쓸모없는 것처럼 보이지만, 2006년에는 브라우저가 오늘날과 같이 통일돼있지 않았다는 점을 고려해야 한다. 제이쿼리는 브러우저 간 공통어를 만들었으며, 이는 커뮤니티가 공통의 토양에서 성장하도록 도와줬다. 선택자 구문외에도 AJAX, 애니메이션, 기타 유틸리티 같은 많은 기능이 핵심 프로젝트에 추가됐고 프론트엔드 개발의 맥가이버 칼이 됐다.
제이쿼리는 쉽게 플러그인할 수 있는 jQueryUI라는 공식 UIKit을 갖고 있어 웹에서 모든 요구 사항을 만족시킬 수 있게 됐다. 제이쿼리는 현대 웹 개발의 초석 역할을 했다.
앵귤러JS
앵귤러JS는 2009년 미스코 헤브리에 의해 부수적인 프로젝트로 개발됐다. 나중에 그는 구글의 직원이 됐고 이런 이유로 앵귤러JS는 지금은 구글 엔지니어가 관리한다. 앵귤러JS는 단일 페이지 애플리케이션을 주류로 만드는 데 큰 역할을 했다.
가장 주목할 만한 기능은 양방향 데이터 바인딩이다. 리스트 1-9에서 이 특성을 살펴볼 수 있다.
<div ng-app="app" ng-controller="ctrl"> Value : <input ng-model="value"> <h1> You entered: {{value}} </h1> </div> <script> angular .module('app', []) .controller('ctrl' [ '$scope', $scope => { $scope.value = 'initial value' } ]) </script>
<div ng-app="app" ng-controller="ctrl">
Value : <input ng-model="value">
<h1> You entered: {{value}} </h1>
</div>
<script>
angular .module('app', [])
.controller('ctrl' [ '$scope', $scope => { $scope.value = 'initial value' } ])
</script>
이 메커니즘의 핵심은 $scope 객체다. $scope의 모든 변경 사항은 DOM에 자동으로 적용된다. 입력 이벤트는 $scope 객체에 새로운 값을 생성한다.

양방향 데이터 바인딩 덕분에 개발자는 웹 애플리케이션을 빠르게 작성할 수 있게 됐다. 그러나 양방향 데이터 바인딩이 대규모 애플리케이션에는 적합하지 않기 때문에 시간이 지나자 많은 개발자가 앵귤러JS를 떠났다.
리액트
2011년 페이스북에서 만들어 2013년 오픈소스로 공개한 리액트는 현재 가장 인기 있는 프론트엔드다.
리스트1-10. 라이프사이클 메서드를 갖고 있는 기본 리액트 구성 요소
import React, { Component } from 'react'
import { render } from 'react-dom'
class Timer extends Component {
constructor(props){
super(props)
this.state = { seconds: 0 }
}
componentDidMount() {
this.interval = setInterval(() => {
const { seconds } = this.state this.setState({
seconds: seconds + 1 })
}, 1000)
}
componentWillUnmount() {
clearInterval(this.interval)
}
render(){
const { seconds } = this.state
return (
<div> Seconds Elapsed : {seconds} </div>
)}
}
const mountNode = document.getElementById('app')
render(<Timer></Timer>, mountNode)
리액트는 선언적 방식으로 동작한다. 일반적으로 DOM을 직접 수정하는 대신 setState 메서드로 상태를 변경하면 리액트가 나머지 작업을 수행한다.
기술적으로 보면 리액트는 프레임워크가 아니라 렌더링 라이브러리다.
앵귤러
앵귤러는 원래 앵귤러JS의 새로운 버전으로 시작됐기 때문에 이전에는 앵귤러2로 불렸다. 하지만 시맨틱 버전 관리를 진지하게 여겼기 때문에 완전히 다른 프레임워크가 됐고 두 버전 사이의 완전히 다른 접근 방식으로 인해 프로젝트가 중단됐다. 2016년 9월 앵귤러2의 첫 번째 릴리스 이후에 팀은 6개월마다 새로운 메이저 버전을 제공하기로 계획한 릴리스 주기를 지키고자 결국 프로젝트 앵귤러의 이름을 바꾸기로 결정했다고 한다.
앵귤러는 엔터프라이즈 세계를 타깃으로 삼았다. 타입스크립트가 앵귤러로 작업할 때 사용하는 표준이었기 때문에 많은 자바와 C# 개발자가 프론트엔드 애플리케이션 개발에 쉽게 접근하는 데 도움이 됐다.
기술 부채
프로젝트에 기능을 추가할 때 여러 옵션이 있는데 어떤 것은 빠르지만 지저분한 반면에 어떤 것은 설계가 잘 됐지만 느리다. 이런 결정이 어떤 영향을 끼치는지 이해하기 쉽도록 워드 커닝햄은 기술 부채(Technical Debt)라는 개념을 도입했다. 기본 개념은 아주 간단한다.
지저분한 솔루션을 선택할수록 부채는 늘어난다.

그림에서 볼 수 있듯이 부채가 발생하기 시작하면 이자로 인해 지속적으로 증가하는 금융 부채와 마찬가지로 기존 기능의 변경이나 새로운 기능의 추가에 따르는 비용은 시간이 지남에 따라 기하급수적으로 늘어난다.
프레임워크 비용
이 책의 저자는 모든 프레임워크가 기술 부채를 갖고 있다고 믿고 있다. 저자는 일반적으로 최적이 아닌 방법을 선택할 때 부채가 발생하기 시작한다고 한다. 여기서 요점은 내 문제를 해결하는 데 있어 다른 사람의 코드가 최적이 될 수는 없다는 것이다. 물론 새로운 프로젝트에서 팀이 처음부터 모든 것을 새로 구축할 수 있는 충분한 시간을 갖고 있는 이상적인 세상에서는 가능한 일이다. 미래에 코드 변경이 어렵다는 측면에서 보면 프레임워크에는 비용이 발생한다. 프레임워크는 아키텍처 자체에 이미 비용을 포함하고 있다. 시간이 지남에 따라 다른 요인으로 인해 변경이 필요하며, 아키텍처 역시 변경돼야 한다. 대부분의 경우 프레임워크는 이런 변경이 필요한 상황에서 장애물이 된다.
기술 투자
금융 세계에서는 부채가 반드시 나쁜 것만을 아니듯이 말한다. 예를 들어 집을 구입할 때 보통 부채인 대출을 활용한다. 이 경우 사람들은 대출을 나쁜 것이 아닌 투자로 간주한다. 반면에 안정적인 직업도 없는 친구가 페라리를 사고자 대출을 받는다면 여러분은 그 친구를 막으려 할 것이다. 중요한 차이점은 부채 자체가 아니라 부채가 필요한 이유라고 한다.
소프트웨어 개발에 있어서도 동일한 메커니즘을 적용한다. 합리적인 이유로 빠른 솔루션을 사용한다면 기술 부채가 아니라 기술 투자가 된다. 합당한 이유로 선정된 프레임워크는 비용이 아니라 자산이다.
'개발 관련 서적 스터디' 카테고리의 다른 글
[RxJS] Subjects (0) | 2022.04.03 |
---|---|
함수, Observable, Promise (0) | 2022.03.20 |
[RxJS] RxJs가 나온 이유??? (0) | 2022.03.09 |
프레임워크 없는 프론트엔드 개발 3장(DOM 이벤트 관리) (0) | 2021.04.27 |
프레임워크 없는 프론트엔드 개발 2장(렌더링) (0) | 2021.04.15 |