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
- Noções básicas de programação orientada a objetos
- Noções básicas de React e React Hooks
- Conhecimento básico de MobX
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)
- Postagem (tem muitos comentários e pertence a um usuário)
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:
- Altere o tipo de variável de membro
byId
deMap
paraobservable.map
de MobX. Ao tornar uma propriedadeobservável
, dizemos ao MobX para observar as mudanças e renderizar novamente os componentes, se necessário. Em nosso caso, estamos usandoobservable.map
, o que significa que receberemos uma atualização quando um registro for adicionado, atualizado ou removido domapa
. - 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. - A propriedade getter
all
é, na verdade, derivada debyId
observável. Precisamos informar ao MobX que é uma propriedade computada decorando-a comcomputed
. - 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}); }; exportar Comentário padrão;{comment.body}
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}); }); exportar Comentário padrão;{comment.body}
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 (); } ); exportar Post padrão;{post.title}
{ellipsisBody? post.body.substr (0, 100): post.body} {ellipsisBody && ( ... leia mais )}
Escrito por {post.user?.Name}
Criação de páginas em nosso aplicativo React
Nosso aplicativo React terá três páginas.
- Página inicial-exibe uma lista de postagens
- Página de postagem-exibe o conteúdo da postagem e os comentários recebidos
- 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) { retornarcarregando...; } Retorna (); }); exportar HomePage padrão;Postagens
{store.post.all.map ((post)=> ())}
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) { retornarcarregando...; } const post=store.post.byId.get (Number (params.postId)); if (! post) { retornarPostagem não encontrada; } Retorna (); }); exportar PostPage padrão;Comentários
{post.comments.map ((comentário)=> ())}
As únicas diferenças que temos aqui são:
- Estamos obtendo o
postId
usando o ganchouseParams
doreact-router-dom
- Estamos carregando a postagem e os comentários da postagem
- 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) { retornarcarregando...; } const user=store.user.byId.get (userId); if (! usuário) { retornarUsuário não encontrado; } Retorna (); }); exportar UserPage padrão;{user.name} • {user.username}
{user.email}
Postagens
{user.posts.map ((post)=> ())}
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 .