Backbone.js을 시작하는데 도움을 드리기 위한 튜토리얼입니다.
들어가며
42seoul 과제 중 ft_transendence 프로젝트의 요구사항에는 프론트엔드를 Backbone.js(이하 Backbone)로 구현하라는 내용이 있습니다.
Backbone은 미니멀하게 구성되어 있어서 학습자체가 어렵지는 않았어요. 그러나 아무래도 react나 vue 같은 프레임워크에 비해 학습 소스가 적어서 아쉬웠습니다. 언제나처럼 '공식문서 보면 되겠지?' 하고 Backbone.js.org 공식문서를 찾아봤지만 getting start 파트에는 개념 설명만 있고 뭔가.. 제가 바랬던 '따라치며 익히는' 설명이 없더라구요 🧐
그래서 Backbone을 처음 다뤄보시는 분들께 도움드리기 위해 간단한 튜토리얼을 작성해봤습니다 🤗
튜토리얼은 2편으로 구성했습니다.
- Backbone의 개념잡기
- Backbone으로 Todo 만들기
개인적으로 Todo 처럼 간단한 무언가를 만들면서 개념을 확인하는 것이 학습에 효과적이었습니다. 튜토리얼 1편과 2편, 그리고 공식문서를 창에 각각 띄워놓고 번갈아가며 참고하시는 것을 권장 드립니다!
Backbone.js란?
Backbone is a library, not a framework, and plays well with others. - backbonejs.org
Backbone은 프론트엔드 자바스크립트 코드의 틀을 만드는데 도움을 주는 경량 자바스크립트 라이브러리입니다.
네, 프레임워크가 아니라 라이브러리입니다. 프레임워크와 라이브러리를 구별하는건 제어 흐름을 누가 쥐고 있느냐에 달려있는데, Backbone은 틀을 만드는데 도움되는 메서드를 제공해주지만 틀을 만드는건 순전히 개발자의 몫이기 때문입니다. 가령 백엔드 프레임워크인 Ruby On Rails는 폴더 구조와 모듈별 위치가 딱 정해져있고 개발자가 Rails 규칙에 맞춰서 코드를 덧붙이는 느낌인 반면, Backbone은 모듈들을 개발자가 원한다면 하나의 HTML 파일에 몰아넣는 것도 가능하죠.
사실 Backbone이 이름처럼 자바스크립트 코드에 '틀'을 제시하기 때문에 프레임워크라고 하는 분들도 있긴 한데.. 뭐 이게 크게 중요한건 아닌 것 같습니다, 용도에 맞게 잘 쓰면 됩니다 :)
Backbone의 특징은 아래와 같습니다.
- MV* 패턴을 따르게 도와줍니다. MVC 패턴과 유사하지만, 컨트롤러 기능이 뷰에 포함됩니다.
- 라이브러리라서 확장성이 좋습니다. 꼭 따라야할 규칙이 minimal하면서 RESTful API를 잘 지원하다보니 다른 프레임워크에도 잘 녹아들 수 있습니다. 따라서 임베디드 위젯부터 대형 웹 애플리케이션까지 사용가능합니다.
- 경량이기 때문에 네트워크가 느린 경우에도 충분히 사용할 수 있으며 학습에 시간이 적게 걸립니다. Backbone의 소스코드를 보면 주석 포함해도 2100줄을 넘지 않습니다. jQuery는 9000줄 넘는 것과는 대조적이죠.
그럼 언제 Backbone을 쓰면 될까? 🤔
Backbone은 순수 자바스크립트나 jQuery만 가지고 개발하다보면 부딛치기 쉬운 상황이 있습니다. 이런 상황들을 해결하고 싶을 때 쓰는게 보통입니다.
-
서버에 대한 HTTP 요청을 줄여야하는 경우
Backbone 같은 MV* 프레임워크는 대게 모든 스크립트, 스타일시트가 포함된 묶음을 한 번 다운로드하고 필요사항을 마크업하고 나서, 백그라운드에서 추가적으로 필요한 작업을 수행하는 식으로 동작합니다. 때문에 서버에 새로운 페이지 전체를 요청하지 않고 이메일 읽기 화면과 메일 쓰기 화면을 전환하는 것이 가능합니다. 그래서 Backbone이 SPA(Single Page Application)를 만드는데 특화되었다고도 합니다.
-
복잡한 UI 설계를 단순화해야할 경우
Backbone은 하나의 view를 이용해서 요청된 모델을 공유하는 여러개의 sub-view를 관리할 수 있습니다.
-
스파게티 코드를 피하고 싶을 경우
Backbone은 개발자가 비즈니스 모델과 인터페이스를 분리하게끔 도와줍니다. Model과 View간에 event-driven communication을 할 수 있도록 설계되어있기 때문입니다.
반면 페이지와 뷰의 렌더링을 위한 모든 데이터를 서버 응답에 의존해서 수행하고 있거나, 자바스크립트와 jQuery를 조금 사용하는 애플리케이션에 MV* 프레임워크를 사용하는 것은 과도하다고 볼 수 있습니다. Backbone을 사용할 만큼 복잡하지 않다면 굳이 Backbone.js를 이용할 필요는 없겠죠.
SPA에 대해서 더 자세하게 알고 싶으시다면 이 블로그를 확인해보세요. SPA 관련 글 중에 가장 깔끔한 것 같습니다 👍
Backbone.js의 구조
Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.
Backbone은 자바스크립트 라이브러리인 underscore.js에 강한 의존성을 가지고 있고, jQuery에 약한 의존성을 가지고 있습니다. 두 라이브러리에 포함된 강력한 메서드들을 활용하면 코딩이 한결 편해집니다.
Backbone의 중요 모듈들은 다음과 같습니다.
- Model: 비즈니스 데이터와 로직을 다룹니다. Model이 변경되면 이벤트가 발생됩니다.
- Views: DOM과 상호작용하는 객체입니다. Model이나 Collecitons의 이벤트를 리스닝해서 액션(메서드)와 연결하는 소위 MVC 패턴의 컨트롤러 역할도 겸합니다.
- Collections: Model들을 그룹지어서 다룰 수 있도록 해줍니다.
- Events: 다른 모듈들에 녹아들어서 이벤트(ex 클릭, 더블클릭)를 발생시키고 청취할 수 있도록 해줍니다.
- Router: 클라이언트 사이드의 페이지들을 라우팅해서 액션과 이벤트를 연결할 수 있도록 해줍니다.
- Sync: 서버와 통신하여 모델을 읽고 쓸 수 있도록 합니다.
모델은 뷰나 컨트롤러와는 무관하게 작성됩니다. 그리고 뷰가 모델을 관찰하고 있다가 모델이 변경되면 적절히 반영합니다. 때문에 모델을 잘 설계해서 만들고 뷰의 모습을 정의해주면 그 다음부터는 지저분하게 모델의 상태에 따르는 코드를 직접 처리할 필요가 없습니다. 공식 홈페이지에 있는 이미지와 설명을 훑으시면 이해하기 쉬우실거에요.
Backbone.Model
Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.
Backbone의 Model(이하 모델)은 상호작용하는 데이터와 이 데이터에 적용되는 로직을 담고 있습니다. 가령 데이터 유효성 검사나 getter나 setter, default 값, 데이터 초기화, 데이터 변환 같은 기능들이 이에 속합니다.
아래 예시처럼 Backbone.Model을 확장(extend)해서 모델 인스턴스를 만들 수 있습니다. 각 요소들이 key-value 값으로 맵핑되는 식이라 직관적입니다.
var Note = Backbone.Model.extend({
initialize: function() { ... },
author: function() { ... },
coordinates: function() { ... },
allowedToEdit: function(account) {
return true;
}
});
모델의 신규 인스턴스를 만들 때는 initialize를 키로 하는 메서드가 호출되어 초기화 작업을 진행합니다. 모델이 변경되었을 때 알림을 받을 수 있도록 모델에 리스너를 바인딩하는 경우가 있는데요, 이 때 initialize 메서드를 쓰면 좋습니다. 그리고 기본 값을 가지고 있는 모델을 만들려면 defaults를 키로 하여 JSON 스타일로 구성해주면 됩니다.
Model 모듈 관련 중요 메서드는 아래와 같습니다.
get
: 모델에서 속성의 값을 가져옵니다.set
: 모델의 속성을 가지고 있는 해시를 설정합니다. 이 속성들이 변경되었을 때, 변경 이벤트가 발생됩니다. 변경 이벤트 발생 없이 속성을 변경하려면.attributes
를 써보세요.toJSON
:JSON.stringify
에 인자로 넘겨줄 수 있도록 모델의 속성을 얕은 복사한 값을 리턴합니다. 실제로 JSON string을 리턴하진 않는 것을 주의해야합니다.validate
: 속성에 값을 넣기 전에 값의 유효성을 검증할 수 있게 해줍니다. 기본적으로save
메서드를 통해서 모델을 저장하거나,if{validate:true}
를 인자로 넘긴set
메서드를 호출할 때 검증이 수행됩니다.
Backbone.Views
Backbone의 View(이하 뷰)는 MVC 패턴의 컨트롤러 역할을 수행합니다. 구체적으로는 이벤트를 액션(메서드)에 연결시키는 역할을 하죠. Backbone을 Backbone 스럽게 구현하려면 뷰로 이벤트를 다루는 방식을 이해하는 것이 Backbone을 이해하는게 중요합니다.
우선 뷰에서 다루는 이벤트를 살펴봅시다. 뷰에서 다루는 이벤트는 트리거되는 대상에 따라 두 가지로 나눌 수 있습니다.
- DOM 요소의 이벤트: Backbone 이벤트 해시로 바인드합니다. 이벤트 해시는 뷰 챕터 하단에 설명해뒀습니다.
- Backbone 이벤트 API로 트리거되는 이벤트: 모델이나 컬렉션 등 객체에 트리거되는 이벤트를 뜻합니다. on 메서드나 listenTo 메서드로 바인드합니다. 이 메서드들은 이벤트 챕터에서 자세히 살펴보겠습니다.
그럼 뷰로 이벤트를 어떻게 다뤄야 할까요? Backbone은 이벤트 해시로 감지한 DOM 요소의 이벤트는 모델을 변경하는 함수와 연결시키고, 이후 모델의 변경을 감지한 뷰가 스스로 DOM 요소를 변경시킬 수 있도록 구현하는 것을 권장합니다. 이벤트 해시로 DOM 요소의 이벤트를 DOM 요소를 변경시키는 함수와 '직접' 연결시켜버릴 수도 있긴 하지만 권장되지 않습니다. 프로젝트 규모가 커질 수록 해당 DOM 요소의 상태를 따로 파악하기 어려워지기 때문입니다.
이어서 살펴봐야할 것은 뷰 객체와 DOM 요소의 연결방식입니다. 모든 뷰 객체는 DOM 요소에 대한 참조인 el 프로퍼티의 형태로 DOM 요소를 가지고 있습니다. el 프로퍼티로 DOM 요소를 연결하는 데는 두 가지 방법이 있습니다.
-
뷰에 연결할 새로운 요소를 만들고나서 DOM에 추가하는 방법
// 예시: tagName, id, 그리고 className 같은 뷰의 속성의 조합으로 요소를 새로 만들었다. var TodosView = Backbone.View.extend({ tagName: 'ul', className: 'container', id: 'todos', }); var todosView = new TodosView(); console.log(todosView.el); //로그 출력: <ul id="todos" class="container"></ul>
만약 tagName, id, className 같은 속성들을 runtime에 결정되게 하고싶으면 이들을 함수로 정의할 수도 있습니다.
-
페이지에 이미 존재하는 요소에 참조를 연결하는 방법
요소와 일치하는 CSS 셀렉터로el
을 설정할 수 있습니다.// 예시 var todosView = new TodosView({el: $('footer')});
참고로 view.$el
속성은 $(view.el)
과 동일하고, view.$(selector)
는 $(view.el).find(selector)
와 동일합니다. 그리고 기존에 있는 Backbone 뷰에 다른 DOM 요소를 적용해야하면 setElement
를 사용하면 됩니다.
중요한 개념인 render 메서드와 이벤트 해시는 따로 짚고 넘어갈게요.
render 메서드
render
메서드는 템플릿을 렌더링하기 위한 로직을 정의하는 함수입니다. Backbone의 일반적인 코드 작성 관례상 render
메서드의 끝에서 this를 반환하는데, 이는 render
가 실행된 뷰를 다른 부모 뷰에서 sub뷰로 쉽게 재사용 가능하도록 할 뿐더러, 전체 목록이 이입된 후에만 각 요소들을 개별적으로 그릴 수 있도록 해줍니다. 뷰의 render 메서드는 모델의 이벤트와 함께 바인딩하는게 보통인데, 이를 통해서 전체 페이지를 새로고침하지 않고, 모델의 변경을 반영할 수 있게 해줍니다.
이벤트 해시
Backbone 이벤트 해시는 '이벤트 이름 셀렉터': '콜백 함수명'과 같이 키-밸류 쌍의 형태로 나타냅니다. click, submit, mouseover, dblclick과 같은 DOM 이벤트 타입을 지원합니다.
//예시
events: {
"keypress .new-todo": "createOnEnter",
"click .clear-completed": "clearCompleted",
"click .toggle-all": "toggleAllComplete",
},
이 때 콜백 함수 내에서 쓰이는 this
는 현재의 뷰 객체를 참조한다는 것을 유의해야합니다.
Backbone.Colleciton
이름에서 느껴지듯이 Collection(이하 컬렉션)은 모델들의 ordered sets입니다. 컬렉션에 속한 모델들 중 하나라도 변경되면 뷰에서 감지하도록 구현할 수 있습니다.
컬렉션을 만들 때는 필요한 인스턴스 속성과 함께 포함된 모델의 타입을 지정하는 속성을 정의합니다.
컬렉션은 add
와 remove
메서드로 컬렉션에 모델을 추가하거나 제거할 수 있습니다. 그리고 add
와 remove
이벤트를 듣기 위해 리스너를 추가하거나 삭제할 수도 있습니다.
var TodosCollection = Backbone.Collection.extend({
model: Todo
url: "/todos" // fetch 함수 실행시킬 때 요청보낼 URL
});
var a = new Todo({title: 'titleA'}),
var b = new Todo({title: 'titleB'}),
var c = new Todo({title: 'titleC'})
var todos = new TodosCollection([a, b]);
// add될 때 이벤트 리스닝
todos.on("add", funciton(todo) {
console.log("I should do " + todo.get("title"))
});
todos.add(c); // console log에 "I should do titleC"라고 출력된다.
todos.remove([a, b]);
그런데 컬렉션을 변경하고 싶은데 리스닝하고 있는 이벤트가 너무 많은 경우엔 어떻게 해야할까요?
reset
메서드를 사용하면 컬렉션의 전체 컨텐츠를 전부 간단하게 변경가능합니다. 이 경우 remove 이벤트나 add 이벤트 대신 reset 이벤트만 발생합니다.
// 기존에 있던 'titleA', 'titleB', 'titleC' Todo들은 모두 삭제되어버리고 'titleReset'하나만 추가된다.
var todos.reset([
{ title: 'titleReset', completed: false }
]);
underscore.js에서는 컬렉션과 연관된 메서드를 많이 제공하니 참고하세요.
컬렉션 메서드로 RESTful 지속성 유지
대부분의 SPA의 데이터는 서버 상에 존재하는 데이터 집합으로부터 도출됩니다. Backbone은 모델이나 컬렉션을 서버와 동기화할 수 있도록 RESTful하게 통신을 주고 받습니다.
-
서버에서 모델 가져오기
Collections.fetch
는 컬렉션의 url 속성에 지정된 URL에 HTTP GET 호출을 보내서 JSON 배열 타입으로 서버에서 모델 집합을 회수합니다(모델 하나가 아닙니다). 이 데이터를 받았을 때, 컬렉션을 업데이트하기 위해set
메서드가 수행됩니다. 만약fetch
메서드의 두번째 인자로{reset: true}
같은 값을 넘겨주면set
메서드 대신reset
메서드가 수행됩니다. -
서버에 모델 저장
업데이트는
Collection.save
메서드를 통해 '모델별'로 수행합니다(모델 집합이 아닙니다).save
메서드는 컬렉션의 URL에 모델의 id를 추가해서 저장할 수 있는 URL을 구성합니다. 그리고 HTTP의 PUT으로 서버에 데이터를 전송합니다. 만일 모델이 브라우저에서 새로 생성된 경우에는 모델의 id가 없기 때문에 컬렉션의 URL을 HTTP POST로 호출합니다. 아 그리고save
메서드가 호출되면 자동적으로 모델의validate
메서드가 호출되고, 검증이 실패한 경우 모델에서 invalid 이벤트가 발생합니다.한편
Model.save
에{patch: true}
옵션을 지정하게 되면 모델 전체를 서버에 업데이트 하는 대신, 변경된 속성만 업데이트할 수 있도록 서버에 HTTP PATCH를 보냅니다.Collection.create
메서드는 새로운 모델을 만들고, 컬렉션에 모델을 추가하고, 이를 서버에 전송할 때 사용됩니다. -
서버에서 모델 삭제
Collection.remove
메서드로 컬렉션 내의 모델을 제거할 수 있습니다. 그리고Collection.destory
메서드로는 서버에 저장된 모델을 삭제할 수도 있습니다.destory
메서드는 컬렉션의 URL로 HTTP DELETE를 보냅니다.
Backbone.Events
Event(이하 이벤트) 모듈은 비즈니스 로직과 유저 인터페이스를 분리시킵니다. 구체적으로는 아래 객체들에 적용되어 발행자(publish)/구독자(subscribe)처럼 행동 할 수 있도록 합니다.
- Backbone.Model
- Backbone.View
- Backbone.Collection
- Backbone.Router
- Backbone.History
덕분에 스파게티 코드가 되는 것을 효과적으로 막을 수 있죠! 그리고 특정 이벤트가 발생했을 때 호출될 수 있도록 함수를 핸들러로 등록할 수 있습니다. 우선 가장 기본적인 이벤트 메서드 세 개를 살펴볼게요.
on
: 이벤트를 구독합니다. 발행자/구독자 모델의 subscribe와 유사합니다.off
: 객체에 바인딩되어 있는 콜백 함수를 제거합니다. 발행자/구독자 모델에서 unsubscribe하는 경우와 유사합니다.trigger
: 이벤트를 트리거합니다. 발행자/구독자 모델에서 publish하는 경우와 유사합니다.
on/off 메서드 보단 listenTo 메서드
on
과 off
메서드는 객체에 직접 콜백을 추가할 수 있는 반면, listenTo
는 하나의 객체에서 또 다른 객체에 바인딩되어 있는 이벤트를 리스닝하도록 지시합니다. 그래서 뷰 객체에서 모델 객체의 이벤트를 감지할 때 많이 쓰이죠.
on
과 off
를 사용하고 있는 상태에서 뷰와 이에 해당하는 모델을 동시에 지웠을 경우 일반적으로 문제가 발생하지는 않지만, 모델에서 이벤트를 받겠다고 등록했던 뷰를 제거했을 때 문제가 발생할 수 있습니다. 모델은 뷰의 콜백 함수를 참조로 가지고 있기 때문에 자바스크립트의 가비지 컬렉터는 메모리에서 뷰를 제거하지 않습니다. 이를 고스트 뷰(ghost view)라고 하는데, 애플리케이션이 구동하는 동안에 모델은 뷰 보다는 메모리에 오래 남아 있기 때문에 야기됩니다. 이를 예방하기 위해서는 1) 모델을 제거하지 않거나 2) off를 이용해서 뷰의 이벤트 핸들러를 제거할 필요가 있습니다. 아니면 아예 근본적으로 3) on 메서드 대신 listenTo
메서드를 활용하는 것을 더 권장합니다. 왜냐면 view를 제거할 때 쓰는 view.remove
메서드는 기본적으로 stopListening
메서드를 묵시적으로 호출하고, 이 stopListening
메서드는 바인드되어 있는 모든 리스너를 풀어주기 때문입니다.
이벤트 콜백 함수에서의 this
키워드 참조 대상
뷰에서 다뤘듯이 뷰 내에서 청취 가능한 이벤트의 타입은 두 가지입니다. 각각 콜백 함수내의 this
키워드의 참조 대상이 다릅니다.
- DOM 이벤트: 이 경우 콜백 함수 내에서
this
는 뷰 객체를 참조합니다. - Backbone 이벤트 API를 사용해서 트리거된 이벤트: 이 경우 콜백 함수 내에서
this
는 리스너를 참조합니다. 가령 Backbone 모델의 메서드가 콜백 함수로 등록되었다면, 그 함수 내에서의this
는 그 모델 객체를 참조합니다.
Backbone.Router
Backbone.Router(이하 라우터)는 클라이언트 사이드에서 페이지간에 라우팅을 구성하고, 액션과 이벤트를 연결시켜줍니다.
Backbone.history
애플리케이션에서 hashchange 이벤트, 쉽게 말해 '뒤로가기' 버튼을 눌렀을 때 이전페이지가 렌더링되도록 처리하려면 Backbone.history를 초기화해야합니다.
Backbone.history.start()
메서드는 Backbone에게 모든 hashchange 이벤트 모니터링을 개시한다고 알리는 역할을 합니다. 그러므로 페이지를 불러올 때는 router가 모두 다 생성된 다음에 Backbone.histroy.start()
메서드나 Backbone.history.start({pushState: true})
메서드를 호출되게끔 구현하세요!
특정 시점에 애플리케이션의 상태를 반영하기 위한 URL을 갱신하고자 할 때 라우터의 navigate()
메서드를 사용할 수 있습니다. 이 메서드는 기본적으로 hashchange 이벤트를 트리거하지 않고 URL을 간단히 갱신합니다. 물론 trigger:true
옵션을 전달하여 URL 일부가 갱신될 때 라우트가 트리거될 수 있도록 navigate()
를 실행하는 것도 가능합니다.
Backbone Sync API
Backbone.sync
메서드는 Backbone이 모델을 읽어오거나 서버에 저장/삭제하려할 때마다 호출하는 메서드입니다. 기본적으로 jQuery.ajax
를 이용해서 RESTful JSON request를 보냅니다.
sync
메서드는 다음 세 가지 파라미터를 가지고 호출됩니다.
- method: CRUD 메서드 ("create", "read", "update", "delete" 중 하나)
- model: Backbone의 모델 객체 or 읽어들일 컬렉션
- options: 성공과 에러 처리 메서드를 포함.
HTTP 파라미터를 jQuery API에서 사용하는 스타일로 설정할 수 있습니다. 일부 레거시 서버들은 JSON 타입의 요청이나 HTTP PUT, DELETE와 같은 동작을 수행하지 못하기 때문에 두 개의 설정 변수를 사용해서 Backbone에서 이러한 동작들을 모방할 수 있습니다.
Backbone.emulateHTTP = false;
// 서버에서 HTTP PUT이나 HTTP DELETE를 지원하지 않으면 true로 설정
Backbone.emulateJSON = false;
// 서버에서 application/json 요청을 지원하지 않으면 true로 설정
참고로 튜토리얼 2편에서 사용하는 Backbone localStorage는 sync 메서드 구현체 중 하나입니다. 브라우저 localStorage를 이용해서 지속성을 구현하죠.
마치며
Backbone 자체는 인기가 식어가고 있다지만(아니 이미 다 식었나..?) MV* 패턴을 가볍게 접하기에 적당한 것 같습니다. 필요한 기능들을 굉장히 미니멀하게, 그래서 어찌보면 우아하게 구현해놓은 느낌이 드네요!
Backbone으로 프로젝트 진행하며 이해가 더 깊어지면 내용을 보충해보겠습니다.
혹시 부정확한 내용이 있다면 댓글로 남겨주시면 감사하겠습니다. 긴 글 읽어주셔서 감사합니다 😊
참고
https://spoqa.github.io/2012/10/23/apply-backbone.html
https://thinkmobiles.com/blog/why-use-backbonejs/
https://webclub.tistory.com/458
https://www.etatvasoft.com/insights/what-is-backbone-js/
http://52.78.22.201/getstarted/backbonejs/
https://www.toptal.com/backbone-js/top-8-common-backbone-js-developer-mistakes
'Dev_articles > Frontend' 카테고리의 다른 글
[Backbone js] 백본 튜토리얼2 - Todo 만들기 (0) | 2021.01.17 |
---|