generated from muhandojeon/study-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[우창완] 챕터 7: 자바스크립트 디자인 패턴 (3/3) (#67)
* 7-3 * 오타 * chapter 8
- Loading branch information
Showing
2 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
# 자바스크립트 MV* 패턴 | ||
|
||
|
||
|
||
## 8.1 MVC 패턴 | ||
|
||
MVC 패턴에서 Model과 View가 Subject, Observer 관계로 결합도를 낮춘 형태 | ||
|
||
* Model | ||
|
||
```js | ||
// Model: 데이터 관리와 비즈니스 로직 | ||
class TodoModel { | ||
constructor() { | ||
this.todos = []; | ||
this.observers = []; | ||
} | ||
|
||
addTodo = (text) => { | ||
this.todos.push({ id: Date.now(), text, completed: false }); | ||
this.notify(); | ||
} | ||
|
||
addObserver = (observer) => { | ||
this.observers.push(observer); | ||
} | ||
|
||
notify = () => { | ||
this.observers.forEach(observer => observer(this.todos)); | ||
} | ||
} | ||
``` | ||
|
||
* Controller | ||
|
||
Controller에서 View에 함수를 binding, model에 observer를 추가한다. | ||
|
||
```js | ||
// Controller: Model과 View 연결 | ||
class TodoController { | ||
constructor(model, view) { | ||
this.model = model; | ||
this.view = view; | ||
|
||
// View의 이벤트를 Model과 연결 | ||
this.view.bindAddTodo(this.handleAddTodo); | ||
this.model.addObserver(this.view.display); | ||
} | ||
|
||
handleAddTodo = (text) => { | ||
this.model.addTodo(text); | ||
} | ||
} | ||
``` | ||
|
||
|
||
|
||
* View | ||
|
||
```js | ||
// View: UI 표시와 사용자 입력 처리 | ||
class TodoView { | ||
constructor() { | ||
this.input = document.createElement("input"); | ||
this.button = document.createElement("button"); | ||
this.list = document.createElement("ul"); | ||
} | ||
|
||
display = (todos) => { | ||
this.list.innerHTML = ""; | ||
todos.forEach(todo => { | ||
const li = document.createElement("li"); | ||
li.textContent = todo.text; | ||
this.list.appendChild(li); | ||
}); | ||
} | ||
|
||
bindAddTodo = (handler) => { | ||
this.button.addEventListener("click", () => { | ||
if (this.input.value) { | ||
handler(this.input.value); | ||
this.input.value = ""; | ||
} | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
|
||
|
||
|
||
|
||
## MVP 패턴 | ||
|
||
MVC 패턴에서 model과 view 가 관찰자 패턴으로 커뮤니케이션 했다면, MVP패턴은 Presenter 계층이 중간에서 조정하는 역할을한다. | ||
|
||
* MVC | ||
|
||
``` | ||
Model ──(Observer 패턴)─→ View | ||
↑ │ | ||
└─────── Controller ──────┘ | ||
``` | ||
|
||
|
||
|
||
* MVP | ||
|
||
``` | ||
Model ←→ Presenter ←→ View | ||
``` | ||
|
||
|
||
|
||
* 계층 별로 테스트 하기가 더 용이 | ||
|
||
* 개인적으로는 더 명시적이어서 개발 인지부하가 더 적을거 같다. | ||
|
||
* 수동적인 VIew 계층 (MVC에서는 데이터 조작에 직접 관여, MVP에서는 관여 여지가 적음) | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### MVVM 모델 | ||
|
||
Mvvm 모델로 오면서, UI 개발자와 서버 개발자가 분리될 수 있었음 | ||
|
||
코드를 살펴봤을 때는 MVC모델에서 Model의<->View의 관찰자 패턴의 ViewModel(presenter) <-> View의 관찰자 패턴으로 이동한 것 같다. | ||
|
||
|
||
|
||
MVP에서 Model과 View의 분리의 장점을 가져가면서도, 단방향 데이터 흐름을 만들 수 있는 것이 `킥`이다 | ||
|
||
|
||
|
||
아래 코드에서 ViewModel이 View를 직접적으로 모르지만, `상태`가 업데이트되었음을 알리고, View에서 render() 를 통해 선언적으로 UI를 표현하는 방식이 react에도 많은 영향을 주지 않았나 싶다. | ||
|
||
```js | ||
class TodoViewModel { | ||
private todos: string[] = []; | ||
private subscribers: Array<() => void> = []; | ||
|
||
// ... 중요한 로직들 | ||
|
||
subscribe(callback: () => void): void { | ||
this.subscribers.push(callback); | ||
} | ||
|
||
private notifySubscribers(): void { | ||
this.subscribers.forEach(callback => callback()); | ||
} | ||
} | ||
|
||
// View | ||
class TodoViewMVVM { | ||
private input: HTMLInputElement; | ||
private list: HTMLUListElement; | ||
|
||
constructor(private viewModel: TodoViewModel) { | ||
// DOM 설정... | ||
|
||
// 이벤트 바인딩 | ||
this.input.addEventListener('keypress', (e) => { | ||
if (e.key === 'Enter') { | ||
const todo = this.input.value; | ||
if (todo) { | ||
this.viewModel.addTodo(todo); | ||
this.input.value = ''; | ||
} | ||
} | ||
}); | ||
|
||
// ViewModel 구독 | ||
this.viewModel.subscribe(() => this.render()); | ||
} | ||
|
||
private render(): void { | ||
const todos = this.viewModel.getTodos(); | ||
this.list.innerHTML = ''; | ||
todos.forEach((todo, index) => { | ||
const li = document.createElement('li'); | ||
li.textContent = todo; | ||
li.addEventListener('click', () => this.viewModel.removeTodo(index)); | ||
this.list.appendChild(li); | ||
}); | ||
} | ||
} | ||
|
||
``` | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|