Pattern MVVM no ReactJS

Pattern MVVM no ReactJS

Olá leitor, na aula passada, você descobriu a importância do Pattern Composition para que as suas aplicações em ReactJS se tornassem cada vez mais modulares.

Hoje, vamos aprender um outro padrão de arquitetura de software chamado de MVVM (Model-View-ViewModel), e como ele pode ser aplicado às nossas aplicações.

O que é o MVVM?

MVVM significa Model-View-ViewModel, um padrão de arquitetura de software que separa claramente a lógica de apresentação da lógica de negócios em aplicações de interface de usuário.

A ideia basicamente é fazer a separação entre arquivos (módulos), de modo a separar tudo o que é tela de usuário (view) do que é lógica (model), e usar um index.jsx como um intermediador (uma espécie de controller na estrutura MVC, que aqui é conhecido como viewModel).

Veremos agora, uma explicação detalhada sobre o que cada componente realiza:

Model (Modelo)

Representa os dados e o estado da aplicação. Isso pode incluir dados do servidor, estado local e qualquer outra informação que a aplicação utilize.

View (Visão)

Corresponde à camada de interface de usuário. É responsável pela apresentação dos dados aos usuários e pela captura de interações do usuário, como cliques e entradas de teclado.

ViewModel (Intermediador)

Atua como um intermediário entre a camada de visão (View) e o modelo de dados (Model).

Ele contém a lógica de apresentação e trabalha para formatar e transformar os dados do modelo para que sejam facilmente apresentados na View.

Além disso, o ViewModel pode conter lógica para tratar eventos de entrada do usuário, realizar validações de dados e gerenciar o estado da View.

De que forma esse padrão pode ajudar nossas aplicações feitas com ReactJS?

Embora esse padrão (MVVM) seja bastante utilizando em outras frameworks como Angular ou em aplicações feitas com C# e Kotlin, nada impede que você adapte esses conceitos em suas aplicações feitas com ReactJS.

O uso desse padrão pode ser útil em aplicações ReactJS, por conta dos seguintes pontos:

Separação de preocupações: MVVM promove a separação clara entre a lógica de apresentação e a lógica de negócios, o que é um princípio importante para manter aplicações ReactJS escaláveis e fáceis de manter.

Reutilização de código: Ao usar ViewModels para gerenciar o estado e a lógica de apresentação, é possível reutilizar lógicas complexas de forma mais eficiente em várias partes da aplicação.

Testabilidade: A separação clara entre View e ViewModel torna mais fácil testar a lógica de apresentação e as interações do usuário de forma isolada dos detalhes de implementação do ReactJS.

Gerenciamento de estado: ReactJS possui seu próprio modelo de gerenciamento de estado através de hooks e contextos. O ViewModel pode ser usado para encapsular o estado e a lógica de manipulação desse estado, ajudando a manter um fluxo de dados mais organizado e previsível.

Legal, agora chega de papo e vamos direto colocar a mão na massa! 🙂

Criando seu projeto de testes

Antes de começarmos, vamos criar um novo projeto em ReactJS dedicado a esta lição. No meu caso criei um novo projeto chamado de padrao-mvvm:

npx create-react-app padrao-mvvm

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁

Criando um componente sem fazer o uso do padrão MVVM

Para começar, vamos criar um novo componente chamado de FormularioDeProduto dentro da pasta components.

FormularioDeProduto > index.jsx:

import React, { useState } from 'react';

const FormularioDeProduto = () => {
 const [nome, setNome] = useState('');
 const [preco, setPreco] = useState('');
 const [descricao, setDescricao] = useState('');

 const handleSubmit = (event) => {
 event.preventDefault();
 // Aqui você pode adicionar a lógica para enviar os dados para algum lugar, como um backend
 console.log({ nome, preco, descricao });
 // Limpa os campos após o envio
 setNome('');
 setPreco('');
 setDescricao('');
 };

 return (
 <form onSubmit={handleSubmit}>
 <div>
 <label htmlFor="nome">Nome do Produto:</label>
 <input
 type="text"
 id="nome"
 value={nome}
 onChange={(e) => setNome(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="preco">Preço:</label>
 <input
 type="text"
 id="preco"
 value={preco}
 onChange={(e) => setPreco(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="descricao">Descrição:</label>
 <textarea
 id="descricao"
 value={descricao}
 onChange={(e) => setDescricao(e.target.value)}
 required
 />
 </div>
 <button type="submit">Enviar</button>
 </form>
 );
};

export default FormularioDeProduto;

Não se esqueça de importar esse componente dentro do App.js:

import FormularioDeProduto from "./components/FormularioDeProduto";

export default function App() {
 return (
 <div>
 <FormularioDeProduto />
 </div>
 );
}

Como podemos ver, nós temos um formulário bem simples que chama uma função chamada handleSubmit para enviar os dados que foram preenchidos pelo usuário.

Refazendo nosso componente dentro do MVVM (na prática)

Pois bem, de acordo com a estrutura MVVM, nós deveriamos separar o nosso componente em diversos outros arquivos, como: view, model e viewModel.

Para isso, você pode fazer a reestruturação da seguinte forma.

Com a pasta do componente FormularioDeProduto em mãos, você vai precisar separar essa tela em dois arquivos principais, e para isso vamos criar três arquivos dentro daquela pasta.

FormularioDeProduto > view.jsx:

import React, { useState } from 'react';

const FormularioDeProdutoView = () => {
 return (
 <form onSubmit={handleSubmit}>
 <div>
 <label htmlFor="nome">Nome do Produto:</label>
 <input
 type="text"
 id="nome"
 value={nome}
 onChange={(e) => setNome(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="preco">Preço:</label>
 <input
 type="text"
 id="preco"
 value={preco}
 onChange={(e) => setPreco(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="descricao">Descrição:</label>
 <textarea
 id="descricao"
 value={descricao}
 onChange={(e) => setDescricao(e.target.value)}
 required
 />
 </div>
 <button type="submit">Enviar</button>
 </form>
 );
};

export default FormularioDeProdutoView;

Perceba que no componente acima, eu excluí todas as lógicas de useState e funções como handleSubmit, pois a ideia deste componente é armazenar somente o JSX da tela (interface).

Observe que nomeamos o nome do nosso componente para FormularioDeProdutoView, indicando que o arquivo se trata de uma view.

FormularioDeProduto > model.js:

import React, { useState } from 'react';

const useFormularioDeProdutoModel = () => {
 const [nome, setNome] = useState('');
 const [preco, setPreco] = useState('');
 const [descricao, setDescricao] = useState('');

 const handleSubmit = (event) => {
 event.preventDefault();
 console.log({ nome, preco, descricao });
 setNome('');
 setPreco('');
 setDescricao('');
 };

 return{
 nome,
 preco,
 descricao,
 setNome,
 setPreco,
 setDescricao,
 handleSubmit
 }
};

export default useFormularioDeProdutoModel;

Aqui nos estamos armazenando e exportando somente a lógica presente nesse arquivo por meio do return.

Note também que usamos o use antes do nome do componente e o Model, de forma a indicar que ele é um model. (useFormularioDeProdutoModel)

Feito isso, nós vamos precisar usar esses métodos que nós criamos dentro da nossa view, e para fazer essa passagem, nós podemos recebê-los via props dentro do FormularioDeProdutoView da seguinte forma:

FormularioDeProduto > view.jsx:

const FormularioDeProdutoView = ({ nome, preco, descricao, setNome, setPreco, setDescricao, handleSubmit }) => {
 return (
 <form onSubmit={handleSubmit}>
 <div>
 <label htmlFor="nome">Nome do Produto:</label>
 <input
 type="text"
 id="nome"
 value={nome}
 onChange={(e) => setNome(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="preco">Preço:</label>
 <input
 type="text"
 id="preco"
 value={preco}
 onChange={(e) => setPreco(e.target.value)}
 required
 />
 </div>
 <div>
 <label htmlFor="descricao">Descrição:</label>
 <textarea
 id="descricao"
 value={descricao}
 onChange={(e) => setDescricao(e.target.value)}
 required
 />
 </div>
 <button type="submit">Enviar</button>
 </form>
 );
};

export default FormularioDeProdutoView;

Perceba que estamos recebendo via props as variáveis nome, preco, descricao, setNome, setPreco, setDescricao, handleSubmit que são justamente aquelas que declaramos no nosso model.

Beleza, mas de que forma o ReactJS vai fazer referencia ao model.js se nem mesmo o estamos importando dentro desse arquivo?

É isso que nós iremos incluir agora 😋

Dentro do index.jsx, nós vamos criar a estrutura do nosso ViewModel, que vai chamar a nossa view e enviar para ela as propriedades advindas do nosso model, e esse processo ele se dá de forma bem simples, observe:

FormularioDeProduto > index.jsx:

import useFormularioDeProdutoModel from './model';
import FormularioDeProdutoView from './view';

const FormularioDeProduto = () => {
 const formularioDeProdutoModel = useFormularioDeProdutoModel();
 return(
 <>
 <FormularioDeProdutoView {...formularioDeProdutoModel} />
 </>
 );
};

export default FormularioDeProduto;

Observe que a pasta do nosso componente conta com 3 arquivos específicos, cada qual tendo uma representatividade diferente:

index.jsx: Nosso viewModel.

Model.js: Nosso Model.

View.jsx: Nossa View.

Por fim, o nosso componente continua funcionamento normalmente como se nada tivesse mudado:

Com isso temos o padrão MVVM implementado dentro do ReactJS com sucesso 🙂

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição você aprendeu a criar suas aplicações usando o padrão MVVM, até a próxima.