JavaScript Avançado

Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção

7 min de leitura

Como Usar Arquitetura de Frontend: Flux, MVC, MVVM e Event-Driven Design em Produção

MVC: O Fundamento Model-View-Controller é o padrão mais tradicional em arquitetura de frontend, separando responsabilidades em três camadas. O Model gerencia dados, a View exibe informações e o Controller orquestra a comunicação entre elas. O fluxo é bidirecional: usuário interage com a View, Controller captura a ação, atualiza o Model, e o Model notifica a View para re-renderizar. Esse ciclo, apesar de simples, pode gerar acoplamento se não for bem implementado. Vamos a um exemplo prático em JavaScript puro: ${user.name} O MVC funciona bem em projetos pequenos e médios, mas começa a mostrar limitações em aplicações complexas onde múltiplas views precisam sincronizar com o mesmo modelo. MVVM: Binding Automático Model-View-ViewModel introduz uma camada intermediária que elimina a comunicação direta entre View e Model. O ViewModel expõe dados reativos que a View observa automaticamente, criando um two-way binding. Diferentemente do MVC, você não precisa notificar manualmente a View. Frameworks como Vue.js implementam MVVM nativamente. O ViewModel contém toda a lógica de

<h2>MVC: O Fundamento</h2>

<p>Model-View-Controller é o padrão mais tradicional em arquitetura de frontend, separando responsabilidades em três camadas. O Model gerencia dados, a View exibe informações e o Controller orquestra a comunicação entre elas.</p>

<p>O fluxo é bidirecional: usuário interage com a View, Controller captura a ação, atualiza o Model, e o Model notifica a View para re-renderizar. Esse ciclo, apesar de simples, pode gerar acoplamento se não for bem implementado. Vamos a um exemplo prático em JavaScript puro:</p>

<pre><code class="language-javascript">// Model

class UserModel {

constructor() {

this.users = [];

this.observers = [];

}

addObserver(observer) {

this.observers.push(observer);

}

addUser(name) {

this.users.push({ id: Date.now(), name });

this.notifyObservers();

}

notifyObservers() {

this.observers.forEach(obs =&gt; obs.update(this.users));

}

}

// View

class UserView {

constructor(elementId) {

this.element = document.getElementById(elementId);

}

render(users) {

this.element.innerHTML = users

.map(user =&gt; &lt;div&gt;${user.name}&lt;/div&gt;)

.join(&#039;&#039;);

}

update(users) {

this.render(users);

}

}

// Controller

class UserController {

constructor(model, view) {

this.model = model;

this.view = view;

this.model.addObserver(this.view);

}

handleAddUser(name) {

this.model.addUser(name);

}

}

// Uso

const model = new UserModel();

const view = new UserView(&#039;app&#039;);

const controller = new UserController(model, view);

controller.handleAddUser(&#039;João&#039;);</code></pre>

<p>O MVC funciona bem em projetos pequenos e médios, mas começa a mostrar limitações em aplicações complexas onde múltiplas views precisam sincronizar com o mesmo modelo.</p>

<h2>MVVM: Binding Automático</h2>

<p>Model-View-ViewModel introduz uma camada intermediária que elimina a comunicação direta entre View e Model. O ViewModel expõe dados reativos que a View observa automaticamente, criando um two-way binding.</p>

<p>Diferentemente do MVC, você não precisa notificar manualmente a View. Frameworks como Vue.js implementam MVVM nativamente. O ViewModel contém toda a lógica de apresentação, deixando a View completamente declarativa. Aqui está um exemplo com Vue 3:</p>

<pre><code class="language-javascript">// ViewModel + View combinados

&lt;template&gt;

&lt;div&gt;

&lt;input v-model=&quot;userInput&quot; placeholder=&quot;Nome&quot; /&gt;

&lt;button @click=&quot;addUser&quot;&gt;Adicionar&lt;/button&gt;

&lt;div v-for=&quot;user in users&quot; :key=&quot;user.id&quot;&gt;

{{ user.name }}

&lt;/div&gt;

&lt;/div&gt;

&lt;/template&gt;

&lt;script setup&gt;

import { ref } from &#039;vue&#039;;

// Model

const users = ref([]);

const userInput = ref(&#039;&#039;);

// ViewModel (aqui mesmo)

const addUser = () =&gt; {

if (userInput.value.trim()) {

users.value.push({

id: Date.now(),

name: userInput.value

});

userInput.value = &#039;&#039;;

}

};

&lt;/script&gt;</code></pre>

<p>O MVVM reduz boilerplate e torna o código mais intuitivo para quem trabalha com interfaces reativas. Porém, em aplicações com estado global complexo, ele pode criar ViewModels inchados e difíceis de testar.</p>

<h2>Flux: Unidirecionalidade Garantida</h2>

<p>Flux é uma arquitetura, não um padrão, que resolve o problema de estado compartilhado através de um fluxo de dados unidirecional: Actions → Dispatcher → Stores → Views. Quando uma View precisa mudar estado, dispara uma Action, nunca modifica diretamente o Store.</p>

<p>Essa rigidez torna o código previsível e facilita debug. Redux popularizou essa ideia, adicionando imutabilidade e uma store centralizada. Veja um exemplo funcional:</p>

<pre><code class="language-javascript">// Action Creator

const ADD_USER = &#039;ADD_USER&#039;;

function addUserAction(name) {

return { type: ADD_USER, payload: name };

}

// Reducer (implementa a lógica)

function usersReducer(state = [], action) {

switch(action.type) {

case ADD_USER:

return [...state, {

id: Date.now(),

name: action.payload

}];

default:

return state;

}

}

// Store (Redux simplificado)

class SimpleStore {

constructor(reducer) {

this.reducer = reducer;

this.state = reducer(undefined, {});

this.listeners = [];

}

dispatch(action) {

this.state = this.reducer(this.state, action);

this.listeners.forEach(listener =&gt; listener(this.state));

}

subscribe(listener) {

this.listeners.push(listener);

}

getState() {

return this.state;

}

}

// Uso

const store = new SimpleStore(usersReducer);

store.subscribe(state =&gt; {

console.log(&#039;Estado atualizado:&#039;, state);

});

store.dispatch(addUserAction(&#039;Maria&#039;));</code></pre>

<p>Flux é ideal para aplicações médias e grandes onde múltiplos componentes compartilham estado. A desvantagem é verbosidade inicial e curva de aprendizado.</p>

<h2>Event-Driven Design: Desacoplamento Total</h2>

<p>Em arquiteturas event-driven, componentes comunicam-se através de eventos, sem conhecimento direto uns dos outros. Um Publisher emite eventos, Subscribers reagem. Isso permite escalabilidade e flexibilidade máximas.</p>

<p>Diferencia-se de Flux porque não há ordem fixa: eventos podem ser capturados por múltiplos listeners, e novos listeners podem ser adicionados sem modificar o sistema. É especialmente poderoso em aplicações complexas e microsserviços frontend. Exemplo prático:</p>

<pre><code class="language-javascript">// Event Emitter pattern

class EventBus {

constructor() {

this.events = {};

}

on(eventName, callback) {

if (!this.events[eventName]) {

this.events[eventName] = [];

}

this.events[eventName].push(callback);

}

emit(eventName, data) {

if (this.events[eventName]) {

this.events[eventName].forEach(callback =&gt; callback(data));

}

}

}

// Componentes desacoplados

const bus = new EventBus();

// Componente que cria usuário

class UserCreator {

constructor(bus) {

this.bus = bus;

}

create(name) {

this.bus.emit(&#039;user:created&#039;, { name, id: Date.now() });

}

}

// Componente que reage

class UserLogger {

constructor(bus) {

bus.on(&#039;user:created&#039;, (user) =&gt; {

console.log(&#039;Novo usuário:&#039;, user.name);

});

}

}

// Uso

const creator = new UserCreator(bus);

const logger = new UserLogger(bus);

creator.create(&#039;Pedro&#039;); // Log: &quot;Novo usuário: Pedro&quot;</code></pre>

<p>Event-driven é menos estruturado que Flux, exigindo disciplina para não virar &quot;callback hell&quot;. Ideal para aplicações modulares onde componentes aparecem e desaparecem dinamicamente.</p>

<h2>Conclusão</h2>

<p>Cada arquitetura resolve problemas específicos: <strong>MVC</strong> é direto e tradicional para projetos simples; <strong>MVVM</strong> oferece reatividade elegante em frameworks modernos; <strong>Flux</strong> garante previsibilidade em aplicações complexas com estado global; <strong>Event-Driven</strong> maximiza desacoplamento e flexibilidade. Na prática, você combina essas abordagens: um app pode usar Flux para estado global, MVVM em componentes e event-driven para cross-cutting concerns. O segredo é escolher a ferramenta certa para cada camada.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://redux.js.org/" target="_blank" rel="noopener noreferrer">Redux Documentation</a></li>

<li><a href="https://vuejs.org/guide/extras/reactivity-in-depth.html" target="_blank" rel="noopener noreferrer">Vue.js Reactivity Guide</a></li>

<li><a href="https://www.patterns.dev/posts/event-driven-architecture/" target="_blank" rel="noopener noreferrer">Event-Driven Architecture Pattern</a></li>

<li><a href="https://www.geeksforgeeks.org/difference-between-mvc-and-mvvm/" target="_blank" rel="noopener noreferrer">MVC vs MVVM - Differences and When to Use Each</a></li>

<li><a href="https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/" target="_blank" rel="noopener noreferrer">Software Architecture Patterns - O&#039;Reilly</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Micro-frontends com React: Module Federation e Arquitetura Distribuída na Prática
Micro-frontends com React: Module Federation e Arquitetura Distribuída na Prática

O que são Micro-frontends e Module Federation Micro-frontends é uma arquitetu...

O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript
O que Todo Dev Deve Saber sobre Template Literal Types e Recursive Types em TypeScript

Template Literal Types em TypeScript Template Literal Types permitem criar ti...

Como Usar Segurança em Node.js: Injeção, SSRF, Path Traversal e Hardening em Produção
Como Usar Segurança em Node.js: Injeção, SSRF, Path Traversal e Hardening em Produção

Injeção em Node.js A injeção é uma das vulnerabilidades mais críticas em apli...