Modernizando o software legado: programação MUD usando Erlang e CloudI
Ciência De Dados E Bancos De Dados
Existem inúmeros artigos por aí debatendo se React ou Angular é a melhor escolha para desenvolvimento web. Precisamos de mais um?
A razão pela qual escrevi este artigo é porque nenhum dos a artigos Publicados já - embora eles contenham grandes insights - aprofundar o suficiente para que um desenvolvedor de front-end prático decida qual deles pode atender às suas necessidades.
Neste artigo, você aprenderá como Angular e React buscam resolver problemas de front-end semelhantes, embora com filosofias muito diferentes, e se escolher um ou outro é meramente uma questão de preferência pessoal. Para compará-los, construiremos o mesmo aplicativo duas vezes, uma vez com Angular e novamente com React.
Dois anos atrás, escrevi um artigo sobre o React Ecossystem . Entre outros pontos, o artigo argumentou que a Angular havia se tornado vítima de “morte por pré-anúncio”. Naquela época, a escolha entre Angular e quase qualquer outra coisa era fácil para qualquer pessoa que não queria que seu projeto rodasse em uma estrutura obsoleta. O Angular 1 estava obsoleto e o Angular 2 nem mesmo estava disponível na versão alfa.
Em retrospecto, os temores eram mais ou menos justificados. Angular 2 mudou dramaticamente e até passou por uma grande reescrita pouco antes do lançamento final.
Dois anos depois, temos o Angular 4 com uma promessa de estabilidade relativa daqui em diante.
O que agora?
Algumas pessoas dizem que comparar React e Angular é como comparar maçãs com laranjas. Enquanto uma é uma biblioteca que lida com visualizações, a outra é uma estrutura completa.
Claro, a maioria React developers irá adicionar algumas bibliotecas ao React para transformá-lo em uma estrutura completa. Então, novamente, o fluxo de trabalho resultante dessa pilha ainda é muito diferente do Angular, portanto, a comparabilidade ainda é limitada.
A maior diferença está na gestão do estado. O Angular vem com vinculação de dados agrupada, enquanto o React hoje é geralmente ampliado pelo Redux para fornecer fluxo de dados unidirecional e trabalhar com dados imutáveis. Essas são abordagens opostas por si mesmas, e inúmeras discussões estão ocorrendo agora se a vinculação mutável / de dados é melhor ou pior do que imutável / unidirecional.
Como o React é notoriamente mais fácil de hackear, decidi, para o propósito desta comparação, construir uma configuração do React que espelhe Angular razoavelmente perto para permitir a comparação lado a lado de trechos de código.
Certos recursos angulares que se destacam, mas não estão no React por padrão são:
Característica | Pacote angular | Biblioteca React |
---|---|---|
Vinculação de dados, injeção de dependência (DI) | @ angular / core | MobX |
Propriedades computadas | rxjs | MobX |
Roteamento baseado em componente | @ angular / roteador | Roteador React v4 |
Componentes de design de material | @ angular / material | React Toolbox |
CSS com escopo para componentes | @ angular / core | Módulos CSS |
Validações de formulário | @ angular / forms | FormState |
Gerador de projeto | @ angular / cli | React Scripts TS |
A vinculação de dados é indiscutivelmente mais fácil de começar do que a abordagem unidirecional. Claro, seria possível ir na direção completamente oposta e usar Restaurado ou mobx-state-tree com React, e ngrx com Angular. Mas isso seria um assunto para outro post.
como escrever algoritmo de aprendizado de máquina
Enquanto o desempenho está em causa, getters simples no Angular estão simplesmente fora de questão conforme são chamados em cada renderização. É possível usar BehaviorSubject a partir de RsJS , que faz o trabalho.
Com React, é possível usar @computed da MobX, que atinge o mesmo objetivo, com uma API indiscutivelmente um pouco melhor.
A injeção de dependência é meio controversa porque vai contra o paradigma React atual de programação funcional e imutabilidade. Acontece que algum tipo de injeção de dependência é quase indispensável em ambientes de vinculação de dados, pois ajuda na dissociação (e, portanto, na simulação e teste), onde não há uma arquitetura de camada de dados separada.
Mais uma vantagem do DI (compatível com Angular) é a capacidade de ter diferentes ciclos de vida de diferentes lojas. A maioria dos paradigmas React atuais usa algum tipo de estado global do aplicativo que mapeia para componentes diferentes, mas, pela minha experiência, é muito fácil introduzir bugs ao limpar o estado global na desmontagem do componente.
Ter uma loja que é criada na montagem do componente (e estar perfeitamente disponível para os filhos deste componente) parece ser realmente útil e um conceito muitas vezes esquecido.
Fora da caixa em Angular, mas facilmente reproduzível com MobX também.
O roteamento baseado em componentes permite que os componentes gerenciem suas próprias sub-rotas em vez de ter uma grande configuração de roteador global. Esta abordagem finalmente chegou a react-router
na versão 4.
É sempre bom começar com alguns componentes de nível superior, e o material design se tornou algo como uma escolha padrão universalmente aceita, mesmo em projetos que não são do Google.
Eu escolhi deliberadamente React Toolbox sobre o normalmente recomendado IU de material , já que a interface de usuário do material tem confissão séria problemas de desempenho com sua abordagem CSS inline, que eles planejam resolver na próxima versão.
Além disso, PostCSS / cssnext usado no React Toolbox está começando a substituir Sass / LESS de qualquer maneira.
As classes CSS são algo como variáveis globais. Existem inúmeras abordagens para organizar CSS para evitar conflitos (incluindo BEM ), mas há uma tendência clara no uso de bibliotecas que ajudam a processar CSS para evitar esses conflitos sem a necessidade de um desenvolvedor front-end para conceber sistemas de nomenclatura CSS elaborados.
As validações de formulário são um recurso não trivial e amplamente utilizado. É bom ter aqueles cobertos por uma biblioteca para evitar a repetição de código e bugs.
Ter um gerador CLI para um projeto é um pouco mais conveniente do que clonar boilerplates do GitHub.
Então, vamos criar o mesmo aplicativo no React e no Angular. Nada espetacular, apenas um Shoutboard que permite a qualquer pessoa postar mensagens em uma página comum.
Você pode experimentar os aplicativos aqui:
Se você quiser ter o código-fonte completo, pode obtê-lo no GitHub:
Você notará que também usamos o TypeScript para o aplicativo React. As vantagens da verificação de tipo no TypeScript são óbvias. E agora, como um melhor manuseio das importações, async / await e rest spread finalmente chegaram ao TypeScript 2, ele sai de Babel / ES7 / Fluxo na poeira.
Além disso, vamos adicionar Cliente Apollo para ambos porque queremos usar GraphQL. Quer dizer, REST é ótimo, mas depois de uma década ou mais, fica velho.
Primeiro, vamos dar uma olhada nos pontos de entrada de ambos os aplicativos.
Angular
const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'posts', component: PostsComponent }, { path: 'form', component: FormComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' } ] @NgModule({ declarations: [ AppComponent, PostsComponent, HomeComponent, FormComponent, ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes), ApolloModule.forRoot(provideClient), FormsModule, ReactiveFormsModule, HttpModule, BrowserAnimationsModule, MdInputModule, MdSelectModule, MdButtonModule, MdCardModule, MdIconModule ], providers: [ AppService ], bootstrap: [AppComponent] })
@Injectable() export class AppService { username = 'Mr. User' }
Basicamente, todos os componentes que queremos usar no aplicativo precisam ir para declarações. Todas as bibliotecas de terceiros para importações e todas as lojas globais para fornecedores. Os componentes filhos têm acesso a tudo isso, com a oportunidade de adicionar mais coisas locais.
Reagir
const appStore = AppStore.getInstance() const routerStore = RouterStore.getInstance() const rootStores = { appStore, routerStore } ReactDOM.render( , document.getElementById('root') )
O componente é usado para injeção de dependência em MobX. Ele salva armazenamentos em contexto para que os componentes do React possam injetá-los posteriormente. Sim, o contexto React pode (indiscutivelmente) ser usado com segurança .
A versão React é um pouco mais curta porque não há declarações de módulo - geralmente, você apenas importa e está pronto para usar. Às vezes, esse tipo de dependência rígida é indesejado (teste), então, para lojas singleton globais, tive que usar este GoF padronizar :
export class AppStore { static instance: AppStore static getInstance() @observable username = 'Mr. User' }
O roteador da Angular é injetável, portanto, pode ser usado de qualquer lugar, não apenas de componentes. Para conseguir o mesmo na reação, usamos o mobx-react-router pacote e injete o routerStore
.
Resumo: A inicialização de ambos os aplicativos é bastante simples. O React tem a vantagem de ser mais simples, usando apenas importações em vez de módulos, mas, como veremos mais tarde, esses módulos podem ser bastante úteis. Fazer singletons manualmente é um pouco incômodo. Quanto à sintaxe de declaração de roteamento, JSON vs. JSX é apenas uma questão de preferência.
Portanto, há dois casos de mudança de rota. Declarativo, usando
elementos e imperativo, chamando a API de roteamento (e, portanto, localização) diretamente.
Angular
Home Posts {this.props.children}
O React Router também pode definir a classe do link ativo com activeClassName
.
Aqui, não podemos fornecer o nome da classe diretamente, porque ele foi tornado único pelo compilador de módulos CSS e precisamos usar o style
ajudante. Mais sobre isso mais tarde.
Como visto acima, o React Router usa o elemento dentro de um elemento. Como o elemento apenas envolve e monta a rota atual, isso significa que as sub-rotas do componente atual são apenas this.props.children
. Então isso também é combinável.
export class FormStore { routerStore: RouterStore constructor() { this.routerStore = RouterStore.getInstance() } goBack = () => { this.routerStore.history.push('/posts') } }
O mobx-router-store
o pacote também permite fácil injeção e navegação.
Resumo: Ambas as abordagens de roteamento são bastante comparáveis. O Angular parece ser mais intuitivo, enquanto o React Router tem uma composição um pouco mais direta.
Já foi comprovado que é benéfico separar a camada de dados da camada de apresentação. O que estamos tentando alcançar com DI aqui é fazer com que os componentes das camadas de dados (aqui chamados de modelo / loja / serviço) sigam o ciclo de vida dos componentes visuais e, assim, permitir fazer uma ou mais instâncias de tais componentes sem a necessidade de toque global Estado. Além disso, deve ser possível misturar e combinar dados compatíveis e camadas de visualização.
Os exemplos neste artigo são muito simples, então todas as coisas de DI podem parecer um exagero, mas são úteis conforme o aplicativo cresce.
Angular
@Injectable() export class HomeService { message = 'Welcome to home page' counter = 0 increment() { this.counter++ } }
Portanto, qualquer classe pode ser @injectable
, e suas propriedades e métodos disponibilizados para os componentes.
@Component({ selector: 'app-home', templateUrl: './home.component.html', providers: [ HomeService ] }) export class HomeComponent { constructor( public homeService: HomeService, public appService: AppService, ) { } }
Ao registrar o HomeService
para o providers
do componente, nós o disponibilizamos exclusivamente para este componente. Não é um singleton agora, mas cada instância do componente receberá uma nova cópia, nova na montagem do componente. Isso significa que não há dados desatualizados do uso anterior.
Em contraste, o AppService
foi registrado no app.module
(veja acima), por isso é um singleton e permanece o mesmo para todos os componentes, durante a vida útil do aplicativo. Ser capaz de controlar o ciclo de vida dos serviços dos componentes é um conceito muito útil, mas pouco apreciado.
DI funciona atribuindo as instâncias de serviço ao construtor do componente, identificado por tipos de TypeScript. Além disso, o public
palavras-chave atribuem automaticamente os parâmetros a this
, de modo que não precisamos escrever aqueles enfadonhos this.homeService = homeService
mais linhas.
Dashboard
Clicks since last visit: {{homeService.counter}} Click!
A sintaxe do template do Angular, sem dúvida bastante elegante. Eu gosto de [()]
atalho, que funciona como uma ligação de dados bidirecional, mas, por baixo do capô, é na verdade uma ligação de atributo + evento. Conforme determina o ciclo de vida de nossos serviços, homeService.counter
irá reiniciar toda vez que navegarmos para fora de /home
, mas o appService.username
permanece e é acessível de qualquer lugar.
Reagir
import { observable } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } }
Com MobX, precisamos adicionar o @observable
decorador para qualquer propriedade que desejamos tornar observável.
@observer export class Home extends React.Component { homeStore: HomeStore componentWillMount() { this.homeStore = new HomeStore() } render() { return } }
Para gerenciar o ciclo de vida corretamente, precisamos trabalhar um pouco mais do que no exemplo do Angular. Envolvemos o HomeComponent
dentro de um Provider
, que recebe uma nova instância de HomeStore
em cada montagem.
interface HomeComponentProps { appStore?: AppStore, homeStore?: HomeStore } @inject('appStore', 'homeStore') @observer export class HomeComponent extends React.Component { render() { const { homeStore, appStore } = this.props return Dashboard
Clicks since last visit: {homeStore.counter} Click! } }
HomeComponent
usa o @observer
decorador para ouvir as alterações em @observable
propriedades.
O mecanismo subjacente disso é bastante interessante, então vamos examiná-lo brevemente aqui. O @observable
decorator substitui uma propriedade em um objeto por getter e setter, o que permite interceptar chamadas. Quando a função de renderização de um @observer
o componente aumentado é chamado, esses getters de propriedades são chamados e eles mantêm uma referência ao componente que os chamou.
Então, quando setter é chamado e o valor é alterado, as funções de renderização dos componentes que usaram a propriedade na última renderização são chamadas. Agora, os dados sobre quais propriedades são usadas são atualizados e todo o ciclo pode ser reiniciado.
Um mecanismo muito simples e de bom desempenho. Explicação mais detalhada Aqui .
O @inject
decorator é usado para injetar appStore
e homeStore
instâncias em adereços de HomeComponent
. Nesse ponto, cada uma dessas lojas tem um ciclo de vida diferente. appStore
é o mesmo durante a vida útil do aplicativo, mas homeStore
é criado recentemente em cada navegação para a rota “/ home”.
O benefício disso é que não é necessário limpar as propriedades manualmente, como é o caso quando todas as lojas são globais, o que é uma dor se a rota for alguma página de 'detalhes' que contém dados completamente diferentes a cada vez.
Resumo: Como o gerenciamento do ciclo de vida do provedor é uma característica inerente do DI da Angular, é, obviamente, mais simples de alcançá-lo lá. A versão React também pode ser usada, mas envolve muito mais clichês.
Reagir
Vamos começar com React neste, tem uma solução mais direta.
import { observable, computed, action } from 'mobx' export class HomeStore { import { observable, computed, action } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } @computed get counterMessage() { console.log('recompute counterMessage!') return `${this.counter} ${this.counter === 1 ? 'click' : 'clicks'} since last visit` } }
Portanto, temos uma propriedade computada que se liga a counter
e retorna uma mensagem devidamente pluralizada. O resultado de counterMessage
é armazenado em cache e recomputado apenas quando counter
alterar.
{homeStore.counterMessage} Click!
Em seguida, referenciamos a propriedade (e increment
método) do modelo JSX. O campo de entrada é conduzido pela vinculação a um valor e permitindo um método de appStore
manipular o evento do usuário.
Angular
Para obter o mesmo efeito no Angular, precisamos ser um pouco mais criativos.
import { Injectable } from '@angular/core' import { BehaviorSubject } from 'rxjs/BehaviorSubject' @Injectable() export class HomeService { message = 'Welcome to home page' counterSubject = new BehaviorSubject(0) // Computed property can serve as basis for further computed properties counterMessage = new BehaviorSubject('') constructor() { // Manually subscribe to each subject that couterMessage depends on this.counterSubject.subscribe(this.recomputeCounterMessage) } // Needs to have bound this private recomputeCounterMessage = (x) => { console.log('recompute counterMessage!') this.counterMessage.next(`${x} ${x === 1 ? 'click' : 'clicks'} since last visit`) } increment() { this.counterSubject.next(this.counterSubject.getValue() + 1) } }
Precisamos definir todos os valores que servem como base para uma propriedade computada como um BehaviorSubject
. A própria propriedade computada também é um BehaviorSubject
, porque qualquer propriedade computada pode servir como entrada para outra propriedade computada.
Claro, RxJS
pode fazer muito mais do que apenas isso, mas seria um assunto para um artigo completamente diferente. A pequena desvantagem é que esse uso trivial de RxJS apenas para propriedades computadas é um pouco mais detalhado do que o exemplo react, e você precisa gerenciar as assinaturas manualmente (como aqui no construtor).
{homeService.counterMessage } Click!
Observe como podemos referenciar o assunto RxJS com | async
tubo. Esse é um toque legal, muito mais curto do que precisar se inscrever em seus componentes. O input
componente é conduzido pelo [(ngModel)]
diretiva. Apesar de parecer estranho, é bastante elegante. Apenas um açúcar sintático para vinculação de dados de valor a appService.username
e atribuição automática de valor a partir do evento de entrada do usuário.
Resumo: As propriedades computadas são mais fáceis de implementar no React / MobX do que no Angular / RxJS, mas o RxJS pode fornecer alguns recursos FRP mais úteis, que podem ser apreciados posteriormente.
Para mostrar como os modelos se comparam, vamos usar o componente Postagens, que exibe uma lista de postagens.
Angular
@Component({ selector: 'app-posts', templateUrl: './posts.component.html', styleUrls: ['./posts.component.css'], providers: [ PostsService ] }) export class PostsComponent implements OnInit { constructor( public postsService: PostsService, public appService: AppService ) { } ngOnInit() { this.postsService.initializePosts() } }
Esse componente apenas conecta HTML, CSS e serviços injetados e também chama a função para carregar postagens da API na inicialização. AppService
é um singleton definido no módulo de aplicativo, enquanto PostsService
é transiente, com uma nova instância criada a cada vez que o componente é criado. O CSS referenciado a partir deste componente tem como escopo este componente, o que significa que o conteúdo não pode afetar nada fora do componente.
add Hello {{appService.username}}
{{post.title}} {{post.name}} {{post.message}}
No modelo HTML, referimos principalmente os componentes do Angular Material. Para tê-los disponíveis, era necessário incluí-los no app.module
importações (ver acima). O *ngFor
é usada para repetir o md-card
componente para cada postagem.
CSS local:
.mat-card { margin-bottom: 1rem; }
O CSS local apenas aumenta uma das classes presentes no md-card
componente.
CSS global:
Calculadora de custos do empregador para compensação do empregado
.float-right { float: right; }
Esta classe é definida em global style.css
arquivo para torná-lo disponível para todos os componentes. Ele pode ser referenciado da maneira padrão, class='float-right'
.
CSS compilado:
.float-right { float: right; } .mat-card[_ngcontent-c1] { margin-bottom: 1rem; }
No CSS compilado, podemos ver que o CSS local foi definido para o componente renderizado usando o [_ngcontent-c1]
seletor de atributo. Cada componente Angular renderizado tem uma classe gerada como esta para fins de escopo CSS.
A vantagem desse mecanismo é que podemos fazer referência a classes normalmente, e o escopo é tratado 'nos bastidores'.
Reagir
import * as style from './posts.css' import * as appStyle from '../app.css' @observer export class Posts extends React.Component { postsStore: PostsStore componentWillMount() { this.postsStore = new PostsStore() this.postsStore.initializePosts() } render() { return } }
No React, novamente, precisamos usar o Provider
abordagem para fazer PostsStore
dependência “transitória”. Também importamos estilos CSS, referenciados como style
e appStyle
, para poder usar as classes desses arquivos CSS em JSX.
interface PostsComponentProps { appStore?: AppStore, postsStore?: PostsStore } @inject('appStore', 'postsStore') @observer export class PostsComponent extends React.Component { render() { const { postsStore, appStore } = this.props return Hello {appStore.username}
{postsStore.posts.map(post => {post.message} )} } }
Naturalmente, o JSX se parece muito mais com o JavaScript do que os modelos HTML do Angular, o que pode ser bom ou ruim, dependendo do seu gosto. Em vez de *ngFor
, usamos a diretiva map
construir para iterar nas postagens.
Agora, Angular pode ser a estrutura que mais elogia o TypeScript, mas é na verdade JSX onde o TypeScript realmente brilha. Com a adição de módulos CSS (importados acima), ele realmente transforma a codificação de seu modelo em um autocompletar de código zen. Cada coisa é verificada por tipo. Componentes, atributos e até classes CSS (appStyle.floatRight
e style.messageCard
, veja abaixo). E, claro, a natureza enxuta do JSX incentiva a divisão em componentes e fragmentos um pouco mais do que os modelos do Angular.
CSS local:
.messageCard { margin-bottom: 1rem; }
CSS global:
.floatRight { float: right; }
CSS compilado:
.floatRight__qItBM { float: right; } .messageCard__1Dt_9 { margin-bottom: 1rem; }
Como você pode ver, o carregador de Módulos CSS corrige cada classe CSS com uma correção aleatória, o que garante a exclusividade. Uma maneira direta de evitar conflitos. As classes são então referenciadas por meio dos objetos importados do webpack. Uma possível desvantagem disso pode ser que você não pode simplesmente criar um CSS com uma classe e aumentá-lo, como fizemos no exemplo do Angular. Por outro lado, isso pode ser realmente uma coisa boa, porque força você a encapsular estilos corretamente.
Resumo: Eu, pessoalmente, gosto de JSX um pouco mais do que modelos Angular, especialmente por causa do autocompletar de código e suporte à verificação de tipo. Esse é realmente um recurso matador. O Angular agora tem o compilador AOT, que também pode detectar algumas coisas, o autocompletar de código também funciona para cerca de metade das coisas lá, mas não é tão completo quanto JSX / TypeScript.
Portanto, decidimos usar GraphQL para armazenar dados para este aplicativo. Uma das maneiras mais fáceis de criar back-end GraphQL é usar algum BaaS, como Graphcool. Então foi isso que fizemos. Basicamente, você apenas define modelos e atributos e seu CRUD está pronto para uso.
Código Comum
Como parte do código relacionado ao GraphQL é 100% igual para ambas as implementações, não vamos repetir isso duas vezes:
const PostsQuery = gql` query PostsQuery { allPosts(orderBy: createdAt_DESC, first: 5) { id, name, title, message } } `
GraphQL é uma linguagem de consulta destinada a fornecer um conjunto mais rico de funcionalidades em comparação com os terminais RESTful clássicos. Vamos dissecar essa questão específica.
PostsQuery
é apenas um nome para referência a essa consulta posteriormente, ele pode receber qualquer nome.allPosts
é a parte mais importante - faz referência à função para consultar todos os registros com o modelo `Post`. Este nome foi criado pela Graphcool.orderBy
e first
são parâmetros do allPosts
função. createdAt
é um dos Post
atributos do modelo. first: 5
significa que ele retornará apenas os primeiros 5 resultados da consulta.id
, name
, title
e message
são os atributos de Post
modelo que queremos incluir no resultado. Outros atributos serão filtrados.Como você já pode ver, é muito poderoso. Verificação de saída esta página para se familiarizar mais com as consultas GraphQL.
interface Post { id: string name: string title: string message: string } interface PostsQueryResult { allPosts: Array }
Sim, como bons cidadãos do TypeScript, criamos interfaces para resultados GraphQL.
Angular
@Injectable() export class PostsService { posts = [] constructor(private apollo: Apollo) { } initializePosts() { this.apollo.query({ query: PostsQuery, fetchPolicy: 'network-only' }).subscribe(({ data }) => { this.posts = data.allPosts }) } }
A consulta GraphQL é um observável RxJS e nós a subscrevemos. Funciona um pouco como uma promessa, mas não exatamente, então estamos sem sorte usando async/await
. Claro, ainda há prometer , mas não parece ser a forma Angular de qualquer maneira. Definimos fetchPolicy: 'network-only'
porque, neste caso, não queremos armazenar os dados em cache, mas buscá-los novamente a cada vez.
Reagir
export class PostsStore { appStore: AppStore @observable posts: Array = [] constructor() { this.appStore = AppStore.getInstance() } async initializePosts() { const result = await this.appStore.apolloClient.query({ query: PostsQuery, fetchPolicy: 'network-only' }) this.posts = result.data.allPosts } }
A versão React é quase idêntica, mas como apolloClient
aqui usa promessas, podemos aproveitar as vantagens do async/await
sintaxe. Existem outras abordagens no React que apenas 'gravam' as consultas GraphQL para componentes de ordem superior , mas pareceu-me misturar os dados e a camada de apresentação um pouco demais.
Resumo: As idéias do RxJS subscribe vs. async / await são realmente as mesmas.
Código Comum
Novamente, algum código relacionado ao GraphQL:
const AddPostMutation = gql` mutation AddPostMutation($name: String!, $title: String!, $message: String!) { createPost( name: $name, title: $title, message: $message ) { id } } `
O objetivo das mutações é criar ou atualizar registros. Portanto, é benéfico declarar algumas variáveis com a mutação porque essas são a maneira de passar os dados para ela. Portanto, temos name
, title
e message
variáveis, digitadas como String
, que precisamos preencher cada vez que chamamos essa mutação. O createPost
função, novamente, é definida pelo Graphcool. Especificamos que Post
as chaves do modelo terão valores de nossas variáveis de mutação, e também que queremos apenas o id
do Post recém-criado a ser enviado em troca.
Angular
@Injectable() export class FormService { constructor( private apollo: Apollo, private router: Router, private appService: AppService ) { } addPost(value) { this.apollo.mutate({ mutation: AddPostMutation, variables: { name: this.appService.username, title: value.title, message: value.message } }).subscribe(({ data }) => { this.router.navigate(['/posts']) }, (error) => { console.log('there was an error sending the query', error) }) } }
Ao chamar apollo.mutate
, precisamos fornecer a mutação que chamamos e as variáveis também. Obtemos o resultado em subscribe
callback e usar o injetado router
para navegar de volta para a lista de postagens.
Reagir
cite os princípios do design
export class FormStore { constructor() { this.appStore = AppStore.getInstance() this.routerStore = RouterStore.getInstance() this.postFormState = new PostFormState() } submit = async () => { await this.postFormState.form.validate() if (this.postFormState.form.error) return const result = await this.appStore.apolloClient.mutate( { mutation: AddPostMutation, variables: { name: this.appStore.username, title: this.postFormState.title.value, message: this.postFormState.message.value } } ) this.goBack() } goBack = () => { this.routerStore.history.push('/posts') } }
Muito semelhante ao anterior, com a diferença de mais injeção de dependência “manual” e o uso de async/await
.
Resumo: Novamente, não há muita diferença aqui. assinar vs. async / await é basicamente tudo o que difere.
Queremos atingir os seguintes objetivos com os formulários neste aplicativo:
Reagir
export const check = (validator, message, options) => (value) => (!validator(value, options) && message) export const checkRequired = (msg: string) => check(nonEmpty, msg) export class PostFormState { title = new FieldState('').validators( checkRequired('Title is required'), check(isLength, 'Title must be at least 4 characters long.', { min: 4 }), check(isLength, 'Title cannot be more than 24 characters long.', { max: 24 }), ) message = new FieldState('').validators( checkRequired('Message cannot be blank.'), check(isLength, 'Message is too short, minimum is 50 characters.', { min: 50 }), check(isLength, 'Message is too long, maximum is 1000 characters.', { max: 1000 }), ) form = new FormState({ title: this.title, message: this.message }) }
Então o formstate A biblioteca funciona da seguinte maneira: Para cada campo do formulário, você define um FieldState
. O parâmetro passado é o valor inicial. O validators
propriedade recebe uma função, que retorna “false” quando o valor é válido e uma mensagem de validação quando o valor não é válido. Com o check
e checkRequired
funções auxiliares, tudo pode parecer bem declarativo.
Para ter a validação de todo o formulário, é benéfico também envolver esses campos com um FormState
instância, que então fornece a validade agregada.
@inject('appStore', 'formStore') @observer export class FormComponent extends React.Component { render() { const { appStore, formStore } = this.props const { postFormState } = formStore return Create a new post
You are now posting as {appStore.username}
O FormState
instância fornece value
, onChange
e error
propriedades, que podem ser facilmente usadas com quaisquer componentes front-end.
} }
Quando form.hasError
é true
, mantemos o botão desativado. O botão de envio envia o formulário para a mutação GraphQL apresentada anteriormente.
Angular
No Angular, usaremos FormService
e FormBuilder
, que são partes de @angular/forms
pacote.
@Component({ selector: 'app-form', templateUrl: './form.component.html', providers: [ FormService ] }) export class FormComponent { postForm: FormGroup validationMessages = { 'title': { 'required': 'Title is required.', 'minlength': 'Title must be at least 4 characters long.', 'maxlength': 'Title cannot be more than 24 characters long.' }, 'message': { 'required': 'Message cannot be blank.', 'minlength': 'Message is too short, minimum is 50 characters', 'maxlength': 'Message is too long, maximum is 1000 characters' } }
Primeiro, vamos definir as mensagens de validação.
constructor( private router: Router, private formService: FormService, public appService: AppService, private fb: FormBuilder, ) { this.createForm() } createForm() { this.postForm = this.fb.group({ title: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(24)] ], message: ['', [Validators.required, Validators.minLength(50), Validators.maxLength(1000)] ], }) }
Usando FormBuilder
, é muito fácil criar a estrutura do formulário, ainda mais sucintamente do que no exemplo React.
get validationErrors() { const errors = {} Object.keys(this.postForm.controls).forEach(key => { errors[key] = '' const control = this.postForm.controls[key] if (control && !control.valid) { const messages = this.validationMessages[key] Object.keys(control.errors).forEach(error => { errors[key] += messages[error] + ' ' }) } }) return errors }
Para colocar as mensagens de validação vinculáveis no lugar certo, precisamos fazer algum processamento. Este código é retirado da documentação oficial, com algumas pequenas alterações. Basicamente, no FormService, os campos mantêm referência apenas aos erros ativos, identificados pelo nome do validador, portanto, precisamos emparelhar manualmente as mensagens necessárias aos campos afetados. Isso não é totalmente uma desvantagem; ele, por exemplo, se presta mais facilmente à internacionalização.
onSubmit({ value, valid }) { if (!valid) { return } this.formService.addPost(value) } onCancel() { this.router.navigate(['/posts']) } }
Novamente, quando o formulário é válido, os dados podem ser enviados para a mutação GraphQL.
Create a new post
You are now posting as {{appService.username}}
{{validationErrors['title']}}
{{validationErrors['message']}}
Cancel Submit
O mais importante é fazer referência ao formGroup que criamos com o FormBuilder, que é o [formGroup]='postForm'
tarefa. Os campos dentro do formulário são vinculados ao modelo de formulário por meio de formControlName
propriedade. Novamente, desabilitamos o botão “Enviar” quando o formulário não é válido. Também precisamos adicionar o cheque sujo, porque aqui, o formulário não sujo ainda pode ser inválido. Queremos que o estado inicial do botão seja “habilitado”.
Resumo: Esta abordagem para formulários no React e Angular é bastante diferente nas frentes de validação e template. A abordagem Angular envolve um pouco mais de “mágica” em vez de vinculação direta, mas, por outro lado, é mais completa e completa.
Oh, mais uma coisa. A produção reduziu os tamanhos de pacote JS, com configurações padrão dos geradores de aplicativos: notavelmente Tree Shaking in React e compilação AOT em Angular.
Bem, não há muita surpresa aqui. Angular sempre foi o mais volumoso.
Ao usar o gzip, os tamanhos caem para 275kb e 127kb, respectivamente.
Basta ter em mente que basicamente todas as bibliotecas de fornecedores. A quantidade de código do aplicativo real é mínima em comparação, o que não é o caso em um aplicativo do mundo real. Nesse caso, a proporção provavelmente seria mais de 1: 2 do que de 1: 4. Além disso, quando você começa a incluir muitas bibliotecas de terceiros com o React, o tamanho do pacote também tende a crescer rapidamente.
Portanto, parece que não fomos capazes (de novo!) De encontrar uma resposta clara sobre se Angular ou React é melhor para desenvolvimento web.
Acontece que os fluxos de trabalho de desenvolvimento no React e no Angular podem ser muito semelhantes, dependendo de quais bibliotecas escolhemos usar o React. Então é principalmente uma questão de preferência pessoal.
Se você gosta de pilhas prontas, injeção de dependência poderosa e planeja usar alguns recursos RxJS, escolha Angular.
Se você gosta de mexer e construir sua pilha sozinho, gosta da simplicidade do JSX e prefere propriedades computáveis mais simples, escolha React / MobX.
Novamente, você pode obter o código-fonte completo do aplicativo neste artigo Aqui e Aqui .
Ou, se você preferir exemplos maiores do RealWorld:
A programação com React / MobX é realmente mais semelhante a Angular do que com React / Redux. Existem algumas diferenças notáveis em modelos e gerenciamento de dependências, mas eles têm o mesmo mutável / vinculação de dados paradigma.
React / Redux com seu imutável / unidirecional paradigma é uma besta completamente diferente.
Não se deixe enganar pelo tamanho reduzido da biblioteca Redux. Pode ser pequeno, mas é uma estrutura, no entanto. A maioria das melhores práticas do Redux hoje estão focadas no uso de bibliotecas compatíveis com redux, como Redux Saga para código assíncrono e busca de dados, Redux Form para gerenciamento de formulários, Selecione novamente para seletores memorizados (valores calculados de Redux). e Recompor entre outros, para um gerenciamento mais refinado do ciclo de vida. Além disso, há uma mudança na comunidade Redux de Immutable.js para Ramda ou Lodash / fp , que funcionam com objetos JS simples em vez de convertê-los.
Um bom exemplo de Redux moderno é o conhecido React Boilerplate . É uma pilha de desenvolvimento formidável, mas se você der uma olhada, é realmente muito, muito diferente de tudo o que vimos neste post até agora.
Eu sinto que o Angular está recebendo um tratamento injusto da parte mais ativa da comunidade JavaScript. Muitas pessoas que expressam insatisfação com ele provavelmente não apreciam a imensa mudança que aconteceu entre o antigo AngularJS e o Angular de hoje. Na minha opinião, é uma estrutura muito limpa e produtiva que tomaria o mundo de assalto se tivesse surgido 1-2 anos antes.
Mesmo assim, a Angular está ganhando espaço, principalmente no mundo corporativo, com grandes equipes e necessidades de padronização e suporte de longo prazo. Ou, dito de outra forma, Angular é como os engenheiros do Google acham que o desenvolvimento web deve ser feito, se isso ainda significa alguma coisa.
Quanto ao MobX, uma avaliação semelhante se aplica. Realmente ótimo, mas subestimado.
Em conclusão: antes de escolher entre React e Angular, escolha primeiro o seu paradigma de programação.
mutável / vinculativo de dados ou imutável / unidirecional , esse ... parece ser o verdadeiro problema.
React é uma biblioteca JavaScript para construir interfaces de usuário. Ele lida com as visualizações e permite que você escolha o resto de sua arquitetura front-end. No entanto, um forte ecossistema de bibliotecas foi desenvolvido em torno dele, permitindo que você construa uma estrutura completa em torno do React adicionando algumas bibliotecas a ele.
Defina todos os valores que servem de base para uma propriedade computada como BehaviorSubject (disponível via RxJS) e inscreva-se manualmente para cada assunto do qual essa propriedade depende.