

MVC 패턴의 다음 단계로, MVC의 단점을 해결하기 위함이다.
MVC 패턴에서 Controller는 View 를 선택만 할 뿐이다.
즉, 직접 업데이트하지 않는다.View 를 업데이트 하기 위해서 아래와 같은 방법들이 있다.
- View가 Model을 직접 이용하여 업데이트
- Model에서 View에게 Notify 하여 업데이트
- View가 Polling 하여 Model의 변화를 감지해서 업데이트
즉 View 를 업데이트 하기 위해서
결국은 Model과 View 사이에서 의존성이 필요해진다.이를 해결하기 위한 기법이 MVP 이다.
🔴 React.js는 기본적으로 MV* 패턴을 따르지 않지만,
Hook 등의 기능을 통해 유사한 구조를 만들 수 있다.지금부터 나올 예시중 일부(MVC,MVP,MVVM) 본인이 이해를 위해서
React 코드를 첨부한 것이니 오해없길 바란다.
Redux를 사용하면 MVC 패턴을 적용할 수 있다.
// reducers/todo.js
export default function todo(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
default:
return state;
}
}
// components/TodoList.js
import React from 'react';
import { useSelector } from 'react-redux';
function TodoList() {
const todos = useSelector(state => state.todos);
return (
<ul>
{todos.map(todo => (
<li key={todo}>{todo}</li>
))}
</ul>
);
}
export default TodoList;
// actions/todo.js
export function addTodo(todo) {
return {
type: 'ADD_TODO',
payload: todo
};
}
// components/AddTodo.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTodo } from '../actions/todo';
function AddTodo() {
const [todo, setTodo] = useState('');
const dispatch = useDispatch();
const handleSubmit = (event) => {
event.preventDefault();
dispatch(addTodo(todo));
setTodo('');
};
return (
<form onSubmit={handleSubmit}>
<input
value={todo}
onChange={event => setTodo(event.target.value)}
/>
<button type="submit">Add</button>
</form>
);
}
export default AddTodo;
Controller에서 Model(reducer)를 업데이트하고 View 를 변경하는 구조

MVC 패턴에서 파생된 모델로,
Model과 View 간의 의존성이 없는 아키텍처 패턴
MVP (Model-View-Presenter) 패턴은 MVC와 비슷하지만,
Controller 대신 Presenter를 사용한다.Presenter는 사용자의 입력을 Model로 전달하고,
Model로부터 받은 데이터를 View에 전달하여 UI를 업데이트한다.
Presenter는 해당 View를 참조하고 이다.
이때 View와 Presenter는 1:1 관계이다.
Presenter는 View와 Model 인스턴스를 가지고, Model과 View 사이의 매개체 역할을 한다.
장점
- Presenter가 M-V 사이에서 관리를 해주기 때문에, MVC 패턴과는 달리 M-V 사이의 의존성이 없다.
단점
- 앱이 커지거나 복잡해질수록 V-P 간 의존성이 강해지는 문제점이 발생한다.
- View와 Presenter가 1:1 관계이기 때문에 서로 간 의존성이 커진다.
- 필요한 클래스 개수가 많다.
TodoModel은 일정 목록을 관리하는 모델
// todoModel.js
export default class TodoModel {
constructor() {
this.todos = [];
}
addTodo(title) {
const newTodo = {
id: Math.random().toString(36).substr(2, 9),
title,
completed: false,
};
this.todos = [...this.todos, newTodo];
}
removeTodo(id) {
this.todos = this.todos.filter((todo) => todo.id !== id);
}
completeTodo(id) {
this.todos = this.todos.map((todo) => (todo.id === id ? {...todo, completed: !todo.completed} : todo));
}
}
TodoView는 사용자 인터페이스를 담당하는 React 함수형 컴포넌트
사용자의 입력에 대한 처리는 Presenter에 위임하고,
Presenter로부터 일정 목록의 업데이트를 받아 화면을 갱신한다.
// React Component
import React, { useState } from 'react';
import TodoPresenter from './TodoPresenter';
const TodoView = () => {
const [todos, setTodos] = useState([]);
const todoPresenter = new TodoPresenter();
const addTodo = (title) => {
const newTodos = todoPresenter.addTodo(title);
setTodos(newTodos);
};
// Render todo list here
// ...
return (
<div>
{/* Implement todo input and submit button here */}
{/* On submit, call `addTodo` with input value */}
</div>
);
};
export default TodoView;
TodoPresenter는 뷰에서 발생하는 이벤트를 처리하고 모델을 업데이트한다.
MVC 에서의 Controller와 유사하나 View 를 업데이트하지 않는다.
// todoPresenter.js
import TodoModel from './TodoModel';
export default class TodoPresenter {
constructor() {
this.model = new TodoModel();
}
addTodo = (title) => {
this.model.addTodo(title);
return this.model.todos;
};
removeTodo = (id) => {
this.model.removeTodo(id);
return this.model.todos;
};
completeTodo = (id) => {
this.model.completeTodo(id);
return this.model.todos;
};
}

MVC에서 파생된, Model과 View 간의 의존성뿐만 아니라
Controller와 View 간의 의존성도 고려하여
각 구성 요소가 독립적으로 작성되고 테스트될 수 있도록 설계된 아키텍처 패턴
- 모든 입력(Input)들은 View로 전달된다.
- ViewModel은 입력에 해당하는 Presentation Logic을 처리하여 View에 데이터를 전달한다.
- ViewModel은 View를 참조하지 않기 때문에 독립적이다.
ViewModel과 View는 1:n 관계이다.- 따라서 View는 자신이 이용할 ViewModel을 선택해 바인딩하여 업데이트를 받게 됩니다.
Command 패턴이나 Data Binding을 이용하여 V-VM 간 의존성을 없앨 수 있습니다.- Model이 변경되면 해당하는 ViewModel을 이용하는 View가 자동으로 업데이트됩니다..
- ViewModel은 View를 나타내 주기 위한 Model이자, View의 Presentation Logic을 처리합니다
React는 단방향 데이터 전달이기 때문에 MVVM을 구현할 수 없다.
MVVM 은 Model과 View 사이에서 위처럼 양방향 데이터 전달을 지원한다.
이 경우 한 View에서 일어난 상호작용 때문에 여러 모델이 변경되거나
그 반대의 일도 벌어질 수 있다.

위 문제점을 해결하기 위해서 FaceBook에서 Flux 패턴을 고안해냈다.
어디서 어느 방향으로 데이터가 전달될지 알지 못할 정도로
혼란한 MVC 패턴의 복잡성을 해소하기 위해,
Flux 패턴에서는 데이터가 한 방향으로만 흐르도록 했다.이러한 것을 지원하기 위해 Redux 라는 것을 만들어냈다
Model은 애플리케이션의 상태(state)와 상태를 변경하는 함수(reducer)를 포함
// Model
const initialState = { todos: [] };
const todoReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TODO':
return { ...state, todos: [...state.todos, action.payload] };
default:
return state;
}
};
View는 사용자에게 보여지는 UI를 담당하고, 사용자의 입력을 ViewModel에 전달합니다:
// View
const TodoList = ({ todos, addTodo }) => {
const [input, setInput] = useState('');
const handleAddTodo = () => {
addTodo(input);
setInput('');
};
return (
<div>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo}</li>
))}
</ul>
</div>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
ViewModel은 Model의 상태를 View에 전달하고, View의 요청을 Model로 전달하는 역할
// ViewModel
const mapStateToProps = (state) => {
return { todos: state.todos };
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (todo) => dispatch({ type: 'ADD_TODO', payload: todo })
};
};