Quando se trata de criar aplicativos maiores, é crucial que o estado do aplicativo seja bem estruturado e claramente definido. Nesta postagem, vamos cobrir como podemos usar MobX para aplicativos de grande escala. Vamos nos concentrar em como estruturar o estado do aplicativo, definir relações entre os dados, fazer chamadas de rede e carregar dados na loja.

Como este artigo se concentra no gerenciamento de estado, não perderemos muito tempo criando componentes de IU/estilo.

Pré-requisitos

Pronto? Vamos começar.

Configurando seu aplicativo empresarial no React e MobX

Primeiro, crie um aplicativo TypeScript React com create-react-app .

 $ npx create-react-app react-mobx-app--template=typescript

Agora, vá para a pasta do aplicativo e instale o seguinte:

 $ yarn adicionar axios react-router-dom mobx mobx-react

Alguns desses tipos de necessidades, então vamos instalá-los também.

 $ yarn add-D @ types/react-router-dom

Vamos excluir os arquivos de que não precisamos. Podemos excluir App.tsx , App.test.tsx , App.css e logo.svg . Criaremos os arquivos necessários novamente mais tarde.

Antes de começarmos a construir o aplicativo, vamos dar uma olhada no que vamos construir e na estrutura da loja. Vamos construir um aplicativo de blog simples. O aplicativo terá três entidades, a saber, usuários, postagens e comentários. Esta é a relação entre eles.

  • Usuário (tem muitas postagens)
    • Postagem (tem muitos comentários e pertence a um usuário)
      • Comentário (pertence a uma postagem)

Criação de tipos de entidade

Agora que conhecemos a estrutura, vamos criar tipos para ela. Vamos colocar os tipos em uma pasta chamada tipos sob src .

Vamos começar criando user.ts em tipos . É assim que parece.

 exportar interface padrão IUser { número de identidade; nome: string; nome de usuário: string; email: string;
}

Em seguida, types/post.ts :

 exportar interface padrão IPost { número de identidade; userId: número; título: string; corpo: string;
}

E, finalmente, types/comment.ts :

 exportar interface padrão IComment { número de identidade; postId: número; nome: string; email: string; corpo: string;
}

A loja de aplicativos

Agora precisamos criar modelos. Esses modelos irão implementar os tipos acima e também definir o relacionamento entre outras entidades. Para definir o relacionamento, esses modelos precisarão de acesso à app store. Mas ainda não criamos a app store. Vamos fazer isso agora.

Crie uma pasta chamada lojas em src na pasta lojas . Agora, crie um arquivo chamado app.ts . Este arquivo irá conter todas as lojas do aplicativo. Por enquanto, vamos torná-la uma classe vazia, assim:

 exportar classe padrão AppStore {}

Criação de modelos

Agora que temos a app store, vamos criar uma pasta chamada modelos em src e começar a implementar nossos modelos.

Primeiro, o modelo do usuário ( models/user.ts ):

 importar AppStore de"../stores/app";
importar IUser de"../types/user"; export default class User implementa IUser { número de identidade; nome: string; nome de usuário: string; email: string; construtor (armazenamento particular: AppStore, usuário: IUser) { this.id=user.id; this.name=user.name; this.username=user.username; this.email=user.email; }
}

Nosso modelo de usuário implementa o tipo de usuário e o construtor usa dois parâmetros. Primeiro, a AppStore para definir relacionamentos e, segundo, o tipo de usuário para instanciar as variáveis ​​de membro.

Da mesma forma, vamos criar um modelo de postagem e um modelo de comentário.

Este é o código para nosso modelo de postagem ( models/post.ts ):

 importar AppStore de"../stores/app";
importar IPost de"../types/post"; export default class Post implementa IPost { número de identidade; userId: número; título: string; corpo: string; construtor (armazenamento privado: AppStore, post: IPost) { this.id=post.id; this.userId=post.userId; this.title=post.title; this.body=post.body; }
}

E aqui está nosso modelo de comentário ( models/comment.ts ):

 importar AppStore de"../stores/app";
importar IComment de"../types/comment"; export default class Comment implementa IComment { número de identidade; postId: número; nome: string; email: string; corpo: string; construtor (loja privada: AppStore, comentário: IComment) { this.id=comment.id; this.postId=comment.postId; this.name=comment.name; this.email=comment.email; this.body=comment.body; }
}

Mas está faltando algo em nossos modelos-os relacionamentos. Não definimos nenhum relacionamento entre eles, mas faremos isso depois de criar as lojas.

Criação de lojas em MobX

Vamos criar as lojas, começando com a loja do usuário. Crie um arquivo chamado user.ts na pasta lojas . Cole o código abaixo:

 importar usuário de"../models/user";
importar IUser de"../types/user";
importar AppStore de"./app"; exportar classe padrão UserStore { byId=novo mapa  (); construtor (armazenamento particular: AppStore) {} carregar (usuários: IUser []) { users.forEach ((it)=> this.byId.set (it.id, novo usuário (this.store, it))); } obter todos () { return Array.from (this.byId.values ​​()); }
}

Deixe-me explicar o que está acontecendo aqui.

Primeiro, temos um mapa chamado byId . Ele vai armazenar todos os nossos registros de usuário digitados por id . Por que mapear? Porque é mais fácil obter, atualizar e remover um registro.

O construtor novamente leva em AppStore para que possa passá-lo para a instância do modelo.

A seguir, temos um método load , que pega em uma matriz do tipo IUser e carrega no mapa byId instanciando o usuário modelo.

E por último, temos uma propriedade getter chamada all . Ele retorna todos os registros do usuário que estão disponíveis na loja.

Observe que nossa loja é de classe simples. Não a tornamos uma loja MobX.

Para fazer isso, faremos as seguintes alterações em nossa loja:

  1. Altere o tipo de variável de membro byId de Map para observable.map de MobX. Ao tornar uma propriedade observável , dizemos ao MobX para observar as mudanças e renderizar novamente os componentes, se necessário. Em nosso caso, estamos usando observable.map , o que significa que receberemos uma atualização quando um registro for adicionado, atualizado ou removido do mapa .
  2. O método load está carregando os dados no armazenamento. Temos que dizer ao MobX que estamos atualizando o observável decorando o método com ação.
  3. A propriedade getter all é, na verdade, derivada de byId observável. Precisamos informar ao MobX que é uma propriedade computada decorando-a com computed .
  4. Finalmente, temos que chamar a função makeObservable passando esta instância no construtor para fazer tudo funcionar.

Depois de fazer as alterações acima, nossa loja ficará assim.

 import { açao, calculado, makeObservable, observável, ObservableMap,
} de"mobx";
importar usuário de"../models/user";
importar IUser de"../types/user";
importar AppStore de"./app"; exportar classe padrão UserStore { byId=observable.map  (); construtor (armazenamento particular: AppStore) { makeObservable (this); } @action load (users: IUser []) { users.forEach ((it)=> this.byId.set (it.id, novo usuário (this.store, it))); } @computed get all () { return Array.from (this.byId.values ​​()); }
}

Da mesma maneira, vamos criar PostStore e CommentStore .

Para stores/post.ts :

 import { açao, calculado, makeObservable, observável, ObservableMap,
} de"mobx";
importar Post de"../models/post";
importar IPost de"../types/post";
importar AppStore de"./app"; exportar classe padrão PostStore { byId=novo observable.map  (); construtor (armazenamento particular: AppStore) { makeObservable (this); } @action load (posts: IPost []) { posts.forEach ((it)=> this.byId.set (it.id, novo Post (this.store, it))); } @computed get all () { return Array.from (this.byId.values ​​()); }
}

Agora, para stores/comment.ts :

 import { açao, calculado, makeObservable, observável, ObservableMap,
} de"mobx"; importar IComment de"../types/comment";
importar Comentário de"../models/comment";
importar AppStore de"./app"; exportar a classe padrão CommentStore { byId=novo observable.map  (); construtor (armazenamento particular: AppStore) { makeObservable (this); } @action load (comments: IComment []) { comments.forEach ((it)=> this.byId.set (it.id, new Comment (this.store, it))); } @computed get all () { return Array.from (this.byId.values ​​()); }
}

Conforme nossas lojas são criadas, vamos instanciá-las na AppStore assim:

 import CommentStore de"./comment";
importar PostStore de"./post";
importar UserStore de"./user"; exportar classe padrão AppStore { usuário=novo UserStore (este); post=novo PostStore (este); comentário=novo CommentStore (este);
}

Agora, vamos voltar à definição da relação entre nossos modelos.

Modelo de usuário : como discutimos anteriormente, o usuário terá muitas postagens. Vamos codificar um relacionamento para isso.

 importar AppStore de"../stores/app";
importar IUser de"../types/user"; export default class User implementa IUser { número de identidade; nome: string; nome de usuário: string; email: string; construtor (armazenamento particular: AppStore, usuário: IUser) { this.id=user.id; this.name=user.name; this.username=user.username; this.email=user.email; } obter postagens () { retornar this.store.post.all.filter ((it)=> it.userId===this.id); }
}

Se você observar nosso relacionamento de postagens, verá que é uma propriedade computada derivada da propriedade computada post.all . Temos que decorá-lo com o decorador computed para MobX para computá-lo e chamar makeObservable no construtor para informar ao MobX que esta classe tem propriedades observáveis ​​.

Aqui está a versão final:

 import {computed, makeObservable} from"mobx";
importar AppStore de"../stores/app";
importar IUser de"../types/user"; export default class User implementa IUser { número de identidade; nome: string; nome de usuário: string; email: string; construtor (armazenamento particular: AppStore, usuário: IUser) { this.id=user.id; this.name=user.name; this.username=user.username; this.email=user.email; makeObservable (this); } @computed get posts () { retornar this.store.post.all.filter ((it)=> it.userId===this.id); }
}

Agora vamos codificar os outros dois modelos também.

Modelo de postagem :

 import {computed, makeObservable} from"mobx";
importar AppStore de"../stores/app";
importar IPost de"../types/post"; export default class Post implementa IPost { número de identidade; userId: número; título: string; corpo: string; construtor (armazenamento privado: AppStore, post: IPost) { this.id=post.id; this.userId=post.userId; this.title=post.title; this.body=post.body; makeObservable (this); } @computed get user () { retornar this.store.user.byId.get (this.userId); } @computed obter comentários () { retornar this.store.comment.all.filter ((it)=> it.postId===this.id); }
}

Modelo de comentário :

 import {computed, makeObservable} from"mobx";
importar AppStore de"../stores/app";
importar IComment de"../types/comment"; export default class Comment implementa IComment { número de identidade; postId: número; nome: string; email: string; corpo: string; construtor (loja privada: AppStore, comentário: IComment) { this.id=comment.id; this.postId=comment.postId; this.name=comment.name; this.email=comment.email; this.body=comment.body; makeObservable (this); } @computed get post () { retornar this.store.post.byId.get (this.postId); }
}

Com isso, criamos com sucesso uma loja inteira para nosso aplicativo!

Codificando a camada de rede

Até agora, criamos lojas para nosso aplicativo, mas a camada de rede está pendente. Agora vamos codificar nossa camada de rede do aplicativo, que será responsável por fazer chamadas de rede e carregar os dados nas lojas.

Vamos separar completamente a camada de rede das lojas. A loja não saberá de onde os dados são carregados.

Vamos começar criando uma classe AppApi principal no arquivo app.ts na pasta src/apis . Ele conterá chamadas de rede de outros recursos.

Este é o código para src/apis/app.ts .

 importar axios de"axios";
importar AppStore de"../stores/app"; exportar classe padrão AppApi { client=axios.create ({baseURL:"https://jsonplaceholder.typicode.com"}); construtor (loja: AppStore) {}
}

Também estamos criando uma variável de membro do cliente axios , que será usada para fazer chamadas de rede.

Usaremos a JSONPlaceholder Fake REST API para obter os dados e definir a base URL para https://jsonplaceholder.typicode.com para nosso cliente axios .

A AppStore é passada para nós no construtor. Estaremos usando AppStore para carregar os dados nas lojas após obter os dados da API.

Agora, temos nosso AppApi . Vamos escrever chamadas de rede para seus recursos individuais. Começaremos com o usuário. Primeiro, crie um arquivo chamado user.ts na pasta src/apis e cole o código abaixo:

 importar AppStore de"../stores/app";
importar AppApi de"./app"; exportar classe padrão UserApi { construtor (API privada: AppApi, armazenamento privado: AppStore) {} async getAll () { const res=await this.api.client.get (`/users`); this.store.user.load (res.data); } getById assíncrono (id: número) { const res=await this.api.client.get (`/users/$ {id}`); this.store.user.load ([res.data]); }
}

Vamos entender o que está acontecendo aqui.

Primeiro, criamos uma classe UserApi , levando em AppApi e AppStore como argumentos do construtor. Em seguida, usaremos a instância AppApi para obter o cliente axios e fazer chamadas de rede. Depois de obter os dados, estamos usando a AppStore para carregar os dados no armazenamento.

Vejamos os métodos individualmente:

getAll -envia uma solicitação get para /users . A resposta é retornada como uma matriz de objetos de usuário, que são carregados na loja usando o método de carregamento da loja.

getById -envia uma solicitação get para /users/$ id para obter um único registro de usuário. A resposta é retornada como um único objeto de usuário, então envolvemos o objeto de usuário em uma matriz colocando colchetes antes de passá-lo para o método de carregamento da loja.

Da mesma forma, criaremos dois outros clientes API para os outros dois recursos, postagem e comentário.

Este é o código para nossa postagem apis/post.ts :

 importar AppStore de"../stores/app";
importar AppApi de"./app"; exportar classe padrão PostApi { construtor (API privada: AppApi, armazenamento privado: AppStore) {} async getAll () { const res=await this.api.client.get (`/posts`); this.store.post.load (res.data); } getById assíncrono (id: número) { const res=await this.api.client.get (`/posts/$ {id}`); this.store.post.load ([res.data]); } async getByUserId (userId: number) { const res=await this.api.client.get (`/posts? userId=$ {userId}`); this.store.post.load (res.data); }
}

E para comentários apis/comment.ts :

 importar AppStore de"../stores/app";
importar AppApi de"./app"; exportar a classe padrão CommentApi { construtor (API privada: AppApi, armazenamento privado: AppStore) {} async getByPostId (postId: number) { const res=await this.api.client.get (`/posts/$ {postId}/comments`); this.store.comment.load (res.data); }
}

Observe que estou escrevendo métodos apenas para chamadas de API necessárias. Você pode adicionar ou remover chamadas com base em sua necessidade.

Agora, terminamos de escrever clientes de API, então vamos registrá-los na classe AppApi .

 importar axios de"axios";
importar AppStore de"../stores/app";
importar CommentApi de"./comment";
importar PostApi de"./post";
importar UserApi de"./user"; exportar classe padrão AppApi { client=axios.create ({baseURL:"https://jsonplaceholder.typicode.com"}); usuário: UserApi; post: PostApi; comentário: CommentApi; construtor (loja: AppStore) { this.user=new UserApi (this, store); this.post=novo PostApi (isto, loja); this.comment=new CommentApi (this, store); }
}

Concluímos a criação da camada de rede para todo o nosso aplicativo.

Contexto do aplicativo para usar lojas e APIs

Criamos as lojas e a camada de rede para nosso aplicativo de blog, mas deve haver uma maneira de usá-las e a API em nossos componentes React. Para isso, usaremos o React Context para fornecer a loja e a API para nossos componentes.

 import React, {useContext} de"react";
importar AppApi de"./apis/app";
importar AppStore de"./stores/app"; interface AppContextType { loja: AppStore; api: AppApi;
} const AppContext=React.createContext  (null); export const useAppContext=()=> { const context=useContext (AppContext); retornar contexto como AppContextType;
}; exportar AppContext padrão;

Isso é muito simples. Criamos um tipo para context , que tem duas propriedades: store e api .

Em seguida, criamos um React Context chamado AppContext para fornecer store e api ao nosso aplicativo. Finalmente, temos um gancho React personalizado useAppContext para utilizar o store e a api em nossos componentes.

Vamos colocar o código acima no arquivo app-context.ts na pasta src .

Usando componentes com MobX

Como não estamos nos concentrando na interface do usuário, os componentes são simples. Estaremos usando conceitos básicos do React como useEffect para callback de montagem de componente, useParams de react-router-dom para obter parâmetros de URL e passaremos instâncias de modelo para componentes como props para exibir dados.

Vamos começar criando o componente de comentário. Colocaremos o arquivo de componentes em src/components com o nome comment.tsx .

 import CommentModel de"../models/comment"; const Comentário: React.FC <{comentário: CommentModel}>=({comentário})=> { Retorna ( 
{comment.name} • {comment.email}

{comment.body}


); }; exportar Comentário padrão;

Nosso componente está faltando algo muito importante. Precisamos informar ao MobX que nosso componente observará as alterações feitas nos observáveis ​​da loja.

Para fazer isso, envolveremos nossos componentes React com o observer do pacote mobx-react . Esta é a aparência do componente atualizado:

 import {observer} de"mobx-react";
import CommentModel de"../models/comment"; const Comentário: React.FC <{comentário: CommentModel}>=observador (({comentário})=> { Retorna ( 
{comment.name} • {comment.email}

{comment.body}


); }); exportar Comentário padrão;

E o componente de postagem ( componentes/post.tsx ):

 import {observer} de"mobx-react";
importar React de"react";
importar {Link} de"react-router-dom";
importar PostModel de"../models/post"; const Post: React.FC <{post: PostModel; ellipsisBody ?: boolean}>=observador ( ({post, ellipsisBody=true})=> { Retorna ( 

{post.title}

{ellipsisBody? post.body.substr (0, 100): post.body} {ellipsisBody && ( ... leia mais )}

Escrito por {post.user?.Name}

); } ); exportar Post padrão;

Criação de páginas em nosso aplicativo React

Nosso aplicativo React terá três páginas.

  1. Página inicial-exibe uma lista de postagens
  2. Página de postagem-exibe o conteúdo da postagem e os comentários recebidos
  3. Página do usuário-exibe informações do usuário e uma lista de postagens escritas pelo usuário

Página inicial

Coloque o arquivo da página inicial em pages/home.tsx.

 import {observer} de"mobx-react";
import {useEffect, useState} de"react";
import {useAppContext} de"../app-context";
importar Postagem de"../components/post"; const HomePage=observador (()=> { const {API, armazenamento}=useAppContext (); const [carregando, setLoading]=useState (false); carga const=assíncrona ()=> { tentar { setLoading (true); aguarde api.post.getAll (); aguarde api.user.getAll (); } finalmente { setLoading (false); } }; useEffect (()=> { carregar(); }, []); if (carregando) { retornar 
carregando...
; } Retorna (

Postagens

{store.post.all.map ((post)=> ( ))}
); }); exportar HomePage padrão;

Aqui, estamos usando o gancho useAppContext para obter store e api . Observe que temos um gancho useState para armazenar o estado de carregamento de nossas chamadas de API. Depois disso, temos uma função de carregamento, que define o carregamento como true , e todas as postagens e usuários e define o carregamento como false .

Finalmente, useEffect com uma matriz vazia como deps para criar um efeito de montagem de componente e chamar a função load nele.

Vamos codificar outras páginas restantes:

Página de postagem (pages/post.tsx)

 import {observer} de"mobx-react";
import {useEffect, useState} de"react";
import {useParams} de"react-router";
import {useAppContext} de"../app-context";
importar Postagem de"../components/post"; const PostPage=observador (()=> { const {API, armazenamento}=useAppContext (); const [carregando, setLoading]=useState (false); const params=useParams <{postId: string}> (); const postId=Número (params.postId); carga const=assíncrona ()=> { tentar { setLoading (true); esperar api.post.getById (postId); aguarde api.comment.getByPostId (postId); } finalmente { setLoading (false); } }; useEffect (()=> { carregar(); }, []); if (carregando) { retornar 
carregando...
; } const post=store.post.byId.get (Number (params.postId)); if (! post) { retornar
Postagem não encontrada
; } Retorna (

Comentários

{post.comments.map ((comentário)=> ( ))}
); }); exportar PostPage padrão;

As únicas diferenças que temos aqui são:

  1. Estamos obtendo o postId usando o gancho useParams do react-router-dom
  2. Estamos carregando a postagem e os comentários da postagem
  3. Na renderização, estamos usando o relacionamento post.comments que criamos em nosso modelo para obter e renderizar todos os comentários para postagem

E, finalmente, vamos codificar a página do usuário.

 import {observer} de"mobx-react";
import {useEffect, useState} de"react";
import {useParams} de"react-router";
import {useAppContext} de"../app-context";
importar Postagem de"../components/post"; const UserPage=observer (()=> { const {API, armazenamento}=useAppContext (); const [carregando, setLoading]=useState (false); const params=useParams <{userId: string}> (); const userId=Number (params.userId); carga const=assíncrona ()=> { tentar { setLoading (true); esperar api.user.getById (userId); esperar api.post.getByUserId (userId); } finalmente { setLoading (false); } }; useEffect (()=> { carregar(); }, []); if (carregando) { retornar 
carregando...
; } const user=store.user.byId.get (userId); if (! usuário) { retornar
Usuário não encontrado
; } Retorna (

{user.name} • {user.username}

{user.email}

Postagens

{user.posts.map ((post)=> ( ))}
); }); exportar UserPage padrão;

Com isso, concluímos com sucesso todo o nosso aplicativo!

Agora, resta apenas mais um item: o componente raiz do aplicativo. Lembre-se de que excluímos o arquivo App.tsx quando começamos. Vamos adicionar de volta à pasta src .

app.tsx

 import {BrowserRouter, Route, Switch} de"react-router-dom";
importar AppContext de"./app-context";
importar AppStore de"./stores/app";
importar AppApi de"./apis/app";
importar HomePage de"./pages/home";
importar PostPage de"./pages/post";
importar UserPage de"./pages/user"; loja const=novo AppStore ();
const api=novo AppApi (loja); function App () { Retorna (          );
} exportar aplicativo padrão;

Estamos instanciando a loja e a API e fornecendo-as ao nosso aplicativo por meio do AppContext.Provider.

Usando BrowserRouter de react-router-dom , renderizaremos nossas páginas. É isso. Se você executar e abrir o aplicativo em um navegador, deverá ver o aplicativo funcionando.

Conclusão

Neste tutorial, aprendemos como gerenciar o estado React em grande escala usando MobX. Obrigado pela leitura e entre em contato comigo se tiver alguma dúvida!

Você pode obter o código-fonte de todo o projeto aqui ou teste o aplicativo ao vivo .

varunpvp/react-mobx-app

Você não pode realizar essa ação no momento. Você fez login com outra guia ou janela. Você saiu em outra guia ou janela. Recarregue para atualizar sua sessão. Recarregue para atualizar sua sessão.

A postagem Usando MobX para empresas de grande porte gerenciamento de estado apareceu primeiro no LogRocket Blog .