> For the complete documentation index, see [llms.txt](https://injun-woo30000.gitbook.io/growth-log/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://injun-woo30000.gitbook.io/growth-log/daily-review/2021/jan/2021-01-23-sat.md).

# 2021-01-23(Sat)

| 항목         | 내용                                                    |
| ---------- | ----------------------------------------------------- |
| 학습 날짜      | 2021-01-23(토)                                         |
| 학습 시간      | 10:00\~24:00                                          |
| 학습 범위 및 주제 | Backbone, Swagger                                     |
| 학습 목표      | Backbone의 View 관리 테크닉을 확인한다. Swagger 사용법을 익혀본다.       |
| 동료 학습 방법   | eunhkim님과 아티클을 공유하고, 팀 프로젝트에 적용시 처리해야할 이슈들에 대해 얘기나눴다. |

## 상세 학습 내용

[백본을 다룰 때 하기 쉬운 8가지 실수](https://www.toptal.com/backbone-js/top-8-common-backbone-js-developer-mistakes)와 [Rendering Views in Backbone.js Isn't Always Simple](https://ianstormtaylor.com/rendering-views-in-backbonejs-isnt-always-simple/) 아티클, 그리고 [Assigning backbone subviews mad even cleaner](https://ianstormtaylor.com/assigning-backbone-subviews-made-even-cleaner/)를 읽었다. 그리고 [왜 이벤트 위임을 해야하는가?](https://ui.toast.com/weekly-pick/ko_20160826)를 읽었다.

### 백본을 다룰 때 하기 쉬운 8가지 실수

#### 실수1) 백본 자체의 코드를 살펴보지 않는다.

생각보다 의존성을 가지고 있는 underscore 라이브러리 자체의 기능이 훌륭하다. [백본의 소스코드](https://www.toptal.com/backbone-js/top-8-common-backbone-js-developer-mistakes)는 커피 한 잔 하며 볼 수 있을만큼 간단하다. 초보 코더들은 이런 잘 작성된 코드를 보면서 배울 수 있다.

#### 실수2) 이벤트에 직접 반응해서 뷰에서 DOM 요소를 직접 변경시킨다.

가령 아래 처럼 코딩할 수 있다.

```
var AudioPlayerControls = Backbone.View.extend({
  events: {
    ‘click .btn-play, .btn-pause’: function(event) {
      $(event.target).toggleClass(‘btn-play btn-pause’)
    }
  },
  // ...
})
```

**이 경우 DOM 요소의 현재 상태를 추적하는데 복잡도가 증가한다는 문제가 생긴다.**

뷰에서 발생하는 이벤트는 모델을 변경시키고, 뷰는 모델의 변경에 반응하도록 하자. 아래처럼!

```
var AudioPlayerControls = Backbone.View.extend({
  events: {
    ‘click .btn-play, .btn-pause’: function(event) {
      this.model.set(‘playing’, !this.model.get(‘playing’))
    }
  },
  initialize: function() {
    this.listenTo(this.model, ‘change’, this.render)
    this.render()
  },
  // ...
})
```

이를 통해 현재 이벤트 상태를 추적할 수 있고, DOM 요소를 구조적으로 관리할 수 있게 된다. 애초에 백본의 설계는 이벤트에 의해 복잡한 DOM 객체가 직접 수정하는 것을 관리하기 위해 고안된 측면이 크다. 😺

#### 실수3) 렌더링 비용을 과소평가한다.

물론 모던 브라우저들은 렌더링 정도는 가볍게 해내지만, 애플리케이션이 비대해지면 문제가 생길 수 있다. 아래 코드를 보자.

```
var AudioPlayerPlaylist = Backbone.View.extend({
  template: _.template(‘<ul> <% _.each(musics, function(m) { %> <li><%- m.title %></li> <% }) %> </ul>’),
  initialize: function() {
    this.listenTo(this.collection, ‘add’, this.render)
  },
  // ...
})
```

매 `add` 이벤트가 발생할 때마다 렌더링이 되는 구조이다. 그럼 모델도 매 `add` 이벤트마다 추가되게되고, 모델이 비대해지며 생기는 성능 저하는 유저 경험을 해칠 수 있다. 음.. 이런 구조보다는 아래처럼 배치 단위로 렌더링하게 수정해보자.

```
this.listenTo(this.collection, ‘add’, _.debounce(_.bind(this.render), 128))
```

여전히 `add` 이벤트가 발생한 횟수만큼 model이 생성되는건 마찬가지이지만, underscore.js 의 `_.debounce` 메서드 덕에 마지막 `add` 이벤트가 발생하고 나서 128 milliseconds만큼 기다린 후 한꺼번에 렌더링할 것이다. 물론 이런 방법 말고도 해결방법은 많을 것이다.

#### 실수4) 쓰고난 이벤트 리스너들을 남겨둔다.

자바스크립트에서 흔히 하듯 `on()` 메서드로 이벤트를 바인드하면 `off()` 메서드로 언바인드하지 않은 상태에서 참조를 잃으면 메모리릭의 원인이 된다. Backbone이 제공하는 `listenTo()` 메서드를 사용하자. 그럼 `remove()` 메서드를 모델에 적용하면 알아서 이벤트 리스너도 제공된다.

#### 실수 5) 단일 뷰 작성

만약 너가 거대한 단일 뷰를 만들어서 그 뷰 위에서 모든 것을 처리하면 백본이 의도하는대로 '간단하게' 코드를 관리할 수 없게 된다. 백본의 뷰는 모델이나 컬렉션과 잘 조화되게끔 기능한다. 이를 힌트삼아 뷰를 여러개의 간단하고 작은 뷰로 나누어 설계하자.

#### 실수 6) 백본을 Non-RESTful APIs 에도 적용할 수 있다는 것을 간과한다.

백본에서 프론트엔드와 백엔드를 연결시켜주는 컴포넌트는 `Sync`다. 이 컴포넌트는 오버라이드할 수 있는 많은 어트리뷰트를 가지고 있다. 백엔드 대신 localStorage와 연결시키는 식으로 오버라이드하면 localStorage에 데이터를 저장할 수 있는 것이다.

#### 실수 7) 데이터를 모델 대신 뷰에 저장한다.

꼭 백엔드에 저장하지 않아도 되는 데이터들은 백본 모델에 저장하자. 뷰에 저장하는 대신 모델에 저장하면 생기는 이점은? 모델에 저장된 데이터의 변경을 일일이 추적해서 뷰를 바꿔줄 필요없이, 연동된 뷰가 알아서 이벤트를 리스닝하고 있다가 변경되도록 셋팅할 수 있다는 것이다.

이렇게 셋팅하지 않으면? 실수가 생기기 쉽다.

#### 실수 8) 이벤트 위임 대신 jQuery의 `.On()` 메소드를 쓴다.

백본에서 자체적으로 DOM 이벤트를 핸들링하는 방법을 쓰는 것이 쓰지 않는 것보다 이점이 많다. 가령 jQuery의 `.On()` 메서드를 활용하면 이벤트를 바인딩하는게 쉽게 느껴질 수 있지만 장기적으로 보면 번거로워지기 쉽다. 해당 요소가 DOM 요소로부터 detach 되면 jQuery는 자동적으로 모든 이벤트 핸들링을 꺼버린다. 이 뜻은 DOM 요소가 detach되었다가 reattach될 때마다 이벤트 바인딩을 새로 해줘야한다는 것을 뜻한다. 반면 백본은 이벤트를 뷰의 root 요소를 통해 바인드하기 때문에 뷰의 DOM 자식 요소들이 변경되어도 새로 바인딩해줄 필요가 없다.

### Rendering Views in Backbone.js Isn’t Always Simple

todo 로는 실제 애플리케이션을 구현하는데 쓸 수 있을만큼 깊이있게 뷰를 렌더링하는 구조를 알려주지 않는다. 아래 요구사항에 맞도록 뷰를 렌더링할 수 있는 구조를 만들고 싶었다.

* 렌더링은 사이드 이펙트없이 여러번 반복 가능해야한다.
* DOM의 순서는 자바스크립트가 아니라 템플릿에 선언되어야 한다.
* 렌더 함수를 반복해서 호출했을 때도 뷰의 상태가 유지해야한다.
* 렌더링을 두 번 했을 때 쓸데 없이 서브뷰가 생성되며 성능이 낭비되지 않아야한다.

#### 문제가 되는 코드 예시

위 요구사항을 충족시키지 못하는 예시들은 아래와 같다.

1. 템플릿을 렌더링할 때 jQuery의 `html()` 메서드를 이용해서 렌더링한다.

   ```
   render : function () {
       this.$el.html(this.template(options));
       return this;
   }
   ```

   이렇게 작성하면 서브뷰가 없을 때는 잘 작동하지만 서브뷰를 작성할 경우 문제가 생길 수 있다. `html()` 메서드가 `empty()` 메서드를 먼저 호출해서 jQuery 이벤트를 모두 unbind 해버리기 때문이다. 의도치않게 `this.events`에 정의된 이벤트들이 모두 사라져버릴 수 있다.
2. 템플릿을 쓰지 않는다.

   ```
   render : function () {
       this.$el.append(this.subview.render().$el);
       this.$el.append(this.anotherSubview.render().$el);
       return this;
   }
   ```

   이렇게 작성하면 DOM 구조가 렌더링 함수를 호출하는 순서에 의존적이게 된다. 결국 프로젝트가 커지면 커질 수록 DOM 구조 관리가 매우 어려워진다. 흠 결국 `html()` 메서드를 써야한다는 건데.. 혼란스럽지 않은가? 이걸 해결하기 위한 방법이 있긴한데, 흔히들..
3. ...오버헤드가 너무 큰 방법을 쓴다.

   ```
   render : function () {
       this.$el.$html(this.template());
   ​
       this.subview        = new Subview();
       this.anotherSubview = new AnotherSubview();
   ​
       this.$el.append(this.subview.render().$el);
       this.$el.append(this.anotherSubview.render().$el);
   ​
       return this;
   }
   ```

   결국 render 함수가 콜될 때마다 render 함수 안에서 서브뷰를 추가하는 방식을 쓰곤한다. 그러면 이 경우 render 함수가 너무 많은 일을 시키는 것이다. 그냥 DOM 이벤트 하나를 rebind하려고 할 때도 서브뷰를 하나하나 생성해줘야할 수도 있다..

#### 그래서 해결책은?

사실 해결책은 간단하다. 그냥 `html()`메서드를 호출할 때마다 서브뷰의 이벤트를 바인드하기 위해 `delegateEvents`를 호출하는 것이다. 이 `delegateEvents`는 백본의 `setElement` 함수에 포함되어있으니 그냥 `setElement` 함수를 호출하자. 아래처럼!

```
render : function () {
    this.$el.$html(this.template());
​
    this.subview.setElement(this.$('.subview')).render();
    this.anotherSubview.setElement(this.$('.another-subview')).render();
    return this;
}
```

그러나 이는 너무 길고 파싱하기도 어렵다. 그래서 아래처럼 베이스뷰에 `assign`이라는 helper 메서드를 만들면 한결 깔끔해진다.

```
render : function () {
    this.$el.html(this.template());
​
    this.assign(this.subview,        '.subview');
    this.assign(this.anotherSubview, '.another-subview');
    return this;
}
```

`assign`은 아래처럼 정의된다.

```
assign : function (view, selector) {
    view.setElement(this.$(selector)).render();
}
```

자, 이제 처음 요구사항이 모두 해소되었다.

* 렌더링은 사이드 이펙트없이 여러번 반복 가능해야한다.
  * `setElement`는 DOM 이벤트를 re-delegate 하므로 더 이상 jQuery의 `html(` 메서드가 서브뷰의 이벤트를 날려버리는 것을 걱정할 필요가 없어진다.
* DOM의 순서는 자바스크립트가 아니라 템플릿에 선언되어야 한다.
  * ㅇㅇ
* 렌더 함수를 반복해서 호출했을 때도 뷰의 상태가 유지해야한다.
  * 뷰가 render 함수가 호출될 때마다 다시 초기화되진 않으므로 뷰의 상태가 유지된다.
* 렌더링을 두 번 했을 때 쓸데 없이 똑같은 렌더링하는 낭비가 없어야한다.
  * 이 방식대로라면 render 함수가 호출될 때마다 쓸데 없이 서브뷰의 생성자 코드를 호출하는 일을 예방할 수 있다.

### Assigning Backbone Subviews Made Even Cleaner

심지어 위에 적었던 것을 아래처럼 간소화시킬 수도 있다.

```
render : function () {
    this.$el.html(this.template());
​
    this.assign({
        '.subview'             : this.subview,
        '.another-subview'     : this.anotherSubview,
        '.yet-another-subview' : this.yetAnotherSubview
    });
​
    return this;
}
```

내부 구현은 아래처럼 되어있다.

```
assign : function (selector, view) {
    var selectors;
​
    if (_.isObject(selector)) {
        selectors = selector;
    } else {
        selectors = {};
        selectors[selector] = view;
    }
​
    if (!selectors) return;
    _.each(selectors, function (view, selector) {
        view.setElement(this.$(selector)).render();
    }, this);
}
```

은휼님과의 탐구 결과

1. DOM 요소 관련 이벤트는 setElement로 위임시킬 수 있다.
2. 올드 DOM 요소에 담겼던 이벤트는 어떻게 되는가? 소스코드를 보자! <https://backbonejs.org/docs/backbone.html> - listenTo 처럼 모델의 이벤트에 바인딩하고 있는 것들은, 뷰 객체가 삭제되기 전까지는 그대로 남아있고, 그래도 무방하므로 이 경우엔 신경쓰지 않아도 된다.

   DOM 요소를 jQuery의 html 메서드로 바꿔끼우면 `empty()` 함수를 선행호출하므로, DOM 요소에 위임된 이벤트들은 확실히 삭제된다. 하지만 위임되지 않은 이벤트가 있을 경우, 삭제되는 것이 보장되지 않으므로 이는 제거하고 html 메서드를 호출해야한다.
3. stopListening을 할 것인가 말 것인가?

   el 옵션으로 기존 DOM 요소를 지정하고 내용을 렌더링하는 뷰이냐에 따라서 취급 방식이 달라져야 한다는 가설로 1차적인 매듭을 지었다. 이벤트라는 것이 특정한 엘리먼트를 기준으로 묶이는 것이기 때문에, 서브 뷰를 포함하고 있는 슈퍼 뷰가 템플릿으로 새롭게 구성된다면 서브 뷰가 이벤트를 걸었던 기존 DOM 요소가 사라지는 현상이 발생하게 되는 것. 그래서 서브 뷰에서 el 옵션을 사용했다면 setElement 메서드를 이용하여 서브 뷰의 el을 갱신해주어야 하고, el 옵션을 사용하지 않았다면 새로 생성한 문서의 지정 영역에 superView\.selector.html(subView\.render().$el)과 같은 방식으로 바로 삽입하면 되는 것 같다.

## 학습 내용에 대한 개인적인 총평

백본을 공식문서와 책으로만 학습했을 때는 떠올리지 못했던 의문들이 많았다.

1. 백본의 뷰를 렌더링하는 패턴의 베스트플랙티스는 무엇일까?
2. SPA를 구성할 때 다른 뷰로 전환시 기존에 생성해둔 뷰 인스턴스는 어떻게 처리해야하는가?
   1. SubView
   2. SubView의 ParentView

이런 의문을 풀기 위해 자료를 찾는 중 은휼님 덕에 좋은 아티클을 찾아 읽을 수 있었다. 역시 프로젝트를 해봐야 좋은 질문이 생기고 질 좋은 성장으로 이어지기 쉽다.

## 다음 학습 계획

* RESTful API
* 협업 규칙 등 관리하는데 필요한 툴 정해보기
