Existem inúmeros artigos por aí debatendo se React ou Angular é a melhor opção para desenvolvimento web. Precisamos de outro?
A razão pela qual escrevi este artigo é porque nenhum dos a artigos já Publicados –Embora contenham grandes ideias– eles vão fundo para um desenvolvedor de front-end prático decidir qual deles pode atender às suas necessidades.
Neste artigo, você aprenderá como Angular e React visam resolver problemas de front-end semelhantes, embora com filosofias muito diferentes, e se escolher um ou outro é simplesmente uma questão de preferência pessoal. Para compará-los, vamos construir o mesmo aplicativo duas vezes, uma vez com Angular e novamente com React.
Dois anos atrás, escrevi um artigo sobre React Ecossystem . Entre outros pontos, o artigo argumenta que a Angular foi vítima de 'morte por anúncio anterior'. Naquela época, a escolha entre o Angular e qualquer outra coisa era fácil para quem não queria que seu projeto funcionasse com uma estrutura desatualizada. O Angular 1 foi descontinuado e o Angular 2 nem estava disponível em 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 a promessa de estabilidade relativa no futuro.
E agora que?
Algumas pessoas dizem que comparar React e Angular é como comparar maçãs com laranjas. Enquanto uma é uma biblioteca que trata de opiniões, a outra é uma estrutura completa.
Claro, a maior parte do React developers eles adicionarão algumas bibliotecas ao React para torná-lo uma estrutura completa. Além disso, o fluxo de trabalho resultante dessa pilha ainda é muito diferente do Angular, portanto, a comparabilidade ainda é limitada.
A maior diferença está na administração estadual. O Angular vem com vinculação de dados incluída, enquanto o React hoje geralmente é ampliado pelo Redux para fornecer fluxo de dados unilateral e trabalhar com dados imutáveis. Essas são abordagens opostas em seu próprio direito, e inúmeras discussões estão começando agora se mutável / união de dados é melhor ou pior do que imutável / unilateral.
Como o React é notoriamente mais fácil de hackear, decidi, para o propósito desta comparação, construir uma configuração do React que reflita Angular razoavelmente perto para permitir a comparação lado a lado de trechos de código.
Alguns recursos angulares que se destacam, mas não são encontrados 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 unilateral. 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.
No que diz respeito ao desempenho, os captadores de nível no Angular estão simplesmente fora de questão, pois são chamados em cada processamento. É possível usar BehaviorSubject a partir de RsJS , que realiza o trabalho.
Com o React, é possível usar @computed da MobX, que atinge o mesmo objetivo, com uma API ligeiramente melhor.
A injeção de dependência é um pouco 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á arquitetura de camada de dados separada.
Mais uma vantagem do DI (suportado pelo Angular) é a capacidade de ter diferentes ciclos de vida de diferentes lojas. A maioria dos paradigmas React atuais usa algum tipo de estado de aplicativo global que mapeia componentes diferentes, mas, pela minha experiência, é muito fácil introduzir erros ao limpar o estado global na desmontagem de componentes.
Ter uma loja construída sobre a montagem de componentes (e perfeitamente disponível para os filhos deste componente) parece ser realmente útil, e o conceito é frequentemente esquecido.
o que é ux ui designer
Fora da caixa no Angular, mas facilmente jogável com MobX também.
O roteamento baseado em componentes permite que os componentes gerenciem suas próprias rotas secundárias 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 última geração, e o material design se tornou uma opção padrão universalmente aceita, mesmo em projetos que não são do Google.
Eu escolhi deliberadamente React Toolbox sobre o recomendado IU de material , uma vez que o material da IU confessa seriamente Problemas de desempenho com seu CSS-in-line, que eles planejam resolver na próxima versão.
Mais distante, PostCSS / cssnext ele foi usado no React Toolbox e está começando a substituir o 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 auxiliem no processamento de CSS para evitar esses conflitos sem a necessidade de um desenvolvedor a parte dianteira para criar 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 erros.
Ter um gerador CLI para um projeto é apenas um pouco mais conveniente do que clonar planilhas de caldeira 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 testar os aplicativos aqui:
Se você quiser ter todo o código-fonte, pode obtê-lo no GitHub:
Você notará que também usamos o TypeScript para o aplicativo React. Os benefícios da verificação de tipo no TypeScript são óbvios. E agora com um melhor manuseio das importações, async / wait and rest propagation finalmente chegou ao TypeScript 2, saindo 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 ele envelhece.
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 as declarações. Todas as livrarias terceirizadas 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 no MobX. Salve os armazenamentos no 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 é apenas importar e você está pronto para começar. Às vezes, esse tipo de dependência rígida é indesejado (em testes), então, para lojas singleton globais, tive que usar isso GoF padronizar década anterior:
export class AppStore { static instance: AppStore static getInstance() (AppStore.instance = new AppStore()) @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 pacote mobx-react-router e injetamos 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 muito úteis. Fazer semifalls manualmente é um pouco chato. Quanto à sintaxe da instrução de roteamento, JSON vs JSX é apenas uma questão de preferência.
Portanto, há dois casos para alterar uma rota. Declarativamente, usando elementos
e, imperativamente, chamando a API de roteamento (e, portanto, de localização) diretamente.
Angular
Home Posts {this.props.children}
O React Router também pode definir a classe ativa do link com activeClassName
.
Aqui, não podemos fornecer o nome da classe diretamente porque ele foi tornado único pelo compilador do módulo CSS e temos que usar o auxiliar style
. Falaremos sobre isso mais tarde.
Como visto acima, o React Router usa o elemento dentro de um elemento. Visto que o elemento simplesmente encapsula e monta o caminho atual, isso significa que os subcaminhos do componente atual são simplesmente this.props.children
. Portanto, também é combinável.
export class FormStore { routerStore: RouterStore constructor() { this.routerStore = RouterStore.getInstance() } goBack = () => { this.routerStore.history.push('/posts') } }
O pacote mobx-router-store
também permite fácil injeção e navegação.
Resumo: Ambas as abordagens de roteamento são bastante comparáveis. Angular parece ser mais intuitivo, enquanto React Router é um pouco mais fácil de compor.
Separar a camada de dados da camada de apresentação já demonstrou ser benéfico. O que estamos tentando conseguir com o DI é 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, portanto, permitir fazer uma ou mais instâncias dos referidos componentes sem tocar no status global . Além disso, deve ser possível misturar e combinar dados com suporte e camadas de exibição.
Os exemplos neste artigo são muito simples, então todas as coisas de DI podem parecer excessivas, 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 @inyectable
, 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
a providers
do componente, o disponibilizamos exclusivamente para este componente. Não é um meio-bug 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 de uso anterior.
Em contraste, o AppService
foi registrado no app.module
(veja acima), por isso é um semi-pousio e permanece o mesmo para todos os componentes, embora a duração da aplicação. Ser capaz de controlar o ciclo de vida dos serviços de componentes é um conceito muito útil, mas pouco apreciado.
DI funciona atribuindo instâncias de serviço ao construtor do componente, identificado por tipos de TypeScript. Além disso, as palavras-chave public
atribuir parâmetros automaticamente a this
, portanto, não precisamos mais digitar essas linhas chatas this.homeService = homeService
.
Dashboard
Clicks since last visit: {{homeService.counter}} Click!
A sintaxe do template do Angular, certamente muito elegante. Eu gosto do atalho [()]
, que funciona como uma ligação de dados bidirecional, mas, por trás disso, é na verdade um atributo binding + event. Conforme ditado pelo ciclo de vida de nossos serviços, homeService.counter
ele reiniciará toda vez que navegarmos para fora de /home
, mas de 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 decorador @observable
a 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 adequadamente, 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 assembleia.
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 decorador @observer
para ouvir as alterações nas propriedades @observable
.
O mecanismo sob o capô disso é muito legal, então vamos examinar isso brevemente aqui. O decorador @observable
sobrescrever uma propriedade em um objeto com getter e setter, permitindo interceptar chamadas. Quando a função de renderização de um componente aumentado é chamada @observador
, as propriedades getters são chamadas e mantêm uma referência ao componente de chamada.
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 decorador @inyectar 'se utiliza para inyectar instancias
appStore y
homeStore en los accesorios de
HomeComponent . En este punto, cada una de esas tiendas tiene un ciclo de vida diferente.
appStore es el mismo durante la vida de la aplicación, pero
homeStore` é criado em cada navegação para o caminho “/ home”.
A vantagem disso é que não é necessário limpar as propriedades manualmente, como é o caso quando todos os armazenamentos são globais, o que é doloroso se o caminho for alguma página de 'detalhes' que contém dados completamente diferentes a cada vez.
Resumo: Como o gerenciamento do ciclo de vida do fornecedor é um recurso inerente do DI da Angular, é claro que é mais simples 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, você 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 calculada que se liga a counter
e retorna uma mensagem corretamente pluralizada. O resultado de counterMessage
é armazenado em cache e recalculado apenas quando counter
alterar.
{homeStore.counterMessage} Click!
Em seguida, referimos a propriedade (e o método increment
) do modelo JSX. O campo de entrada é controlado pela vinculação a um valor e permite que um método `appStore 'manipule 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 de base para uma propriedade calculada como BehaviorSubject
. A própria propriedade calculada também é um BehaviorSubject
, porque qualquer propriedade calculada pode servir como entrada para outra propriedade calculada.
Claro, RxJS
pode fazer muito mais do que apenas isso, mas seria um assunto para um artigo completamente diferente. A desvantagem secundária é que esse uso trivial de RxJS para propriedades calculadas é um pouco mais prolixo do que o exemplo reativo e você precisa gerenciar as assinaturas manualmente (como aqui no construtor).
{ async} Click!
Observe como podemos referenciar o assunto RxJS com |asíncrona
. Esse é um toque legal, muito mais curto do que precisar se inscrever em seus componentes. O componente input
é conduzido pela diretiva [(ngModel)]
. Apesar de parecer estranho, é bastante estiloso. Apenas um açúcar sintático para vincular dados de valor a appService.username
e o valor de atribuição automática do evento de entrada do usuário.
Resumo: As propriedades calculadas 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 são empilhados, usaremos o componente de mensagens, que exibe uma lista de mensagens.
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() } }
Este componente conecta apenas HTML, CSS e serviços injetados e também chama a função para carregar as mensagens da API na inicialização. AppService
é um semi-erro definido no módulo de aplicativo, enquanto PostsService
é transitório, com uma nova instância criada a cada vez que o componente é criado. O CSS que é 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, nos referimos principalmente aos componentes de Material Angular. Para tê-los disponíveis, era necessário incluí-los nas importações app.module
(Veja acima). A diretiva *ngFor
é usado para repetir o componente md-card
para cada posição.
CSS local:
.mat-card { margin-bottom: 1rem; }
O CSS local aumenta apenas uma das classes presentes no componente md-card
.
CSS global:
.float-right { float: right; }
Esta classe é definida no arquivo global style.css
para que esteja disponível para todos os componentes. Ele pode ser referenciado no formato 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 delimitado ao componente renderizado pelo seletor de atributo [_ngcontent-c1]
. 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
para tornar a dependência de PostsStore 'transitória'. Também importamos estilos CSS, denominados 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 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 da diretiva *ngFor
, usamos a construção map
para iterar nas postagens.
Agora, Angular pode ser a estrutura que mais promove o TypeScript, mas na verdade é o JSX onde o TypScript realmente se destaca. Com a adição de módulos CSS (importados acima), realmente transforma a codificação de seu modelo em código zen. Cada coisa é verificada. Componentes, atributos e até classes CSS (appStyle.floatRight
e style.messageCard
, veja abaixo). E, claro, a natureza estreita do JSX incentiva a divisão em componentes e pedaços 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 Loader for CSS Modules faz o postfix de cada classe CSS com um postfix aleatório, o que garante exclusividade. Uma maneira simples de evitar conflitos. As classes são então referenciadas por meio dos objetos importados do pacote da web. Uma possível desvantagem disso pode ser que você não pode criar um CSS com uma classe e aumentá-la, como fizemos no exemplo do Angular. Por outro lado, isso pode ser muito bom, porque força você a encapsular os estilos corretamente.
Resumo: Pessoalmente, gosto de JSX um pouco mais do que de modelos Angular, principalmente devido ao preenchimento de código e ao tipo de suporte de controle. Esse é realmente um recurso matador. O Angular agora tem o compilador AOT, que pode detectar algumas coisas também, o autocompletar de código funciona para cerca de metade das coisas lá também, 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 o 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 usar.
Código Comum
Como parte do código relacionado ao GraphQL é 100% igual para ambas as implementações, não o repetimos 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 aos terminais RESTful clássicos. Vamos dissecar essa questão em particular.
PostsQuery
é apenas um nome para esta consulta para referência posterior, você pode nomear qualquer coisa.allPosts
é a parte mais importante - refere-se à função de consultar todos os registros com o padrão `Post`. Este nome foi criado pela Graphcool.orderBy
e first
são parâmetros da função allPosts
. createdAt
é um dos Post
atributos do modelo. first: 5
significa que ele retornará apenas os 5 primeiros resultados da consulta.id
, name
, title
e message
são os atributos do modelo Post
que queremos ser incluídos no resultado. Outros atributos serão filtrados.Como você já pode ver, é bastante poderoso. Confira 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 RxJS observável e nós o assinamos. Funciona um pouco como uma promessa, mas não funciona, por isso não temos sorte com async/await
. Claro que ainda existem prometer , mas não parece ser o caminho 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 ele usa promessas, podemos tirar proveito da sintaxe async / await
. Existem outras abordagens no React que apenas 'gravam' consultas GraphQL para componentes de ordem superior , mas descobri que misturar muito a camada de dados e a apresentação.
Em resumo: As ideias do RxJS subscribe vs async / await são realmente as mesmas.
Código Comum
Mais uma vez, algum código GraphQL relacionado:
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, pois são a forma de passar os dados nela. Portanto, temos as variáveis name
, title
e message
, escrito como String
, que precisamos preencher toda vez que chamarmos essa mutação. A função createPost
, novamente, é definida pelo Graphcool. Especificamos que as chaves do modelo Post
terá valores das variáveis de mutação de saída, e também que queremos o id
do Post recém-criado é 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) }) } }
Quando chamamos apollo.mutate
, precisamos fornecer a mutação que chamamos e as variáveis também. Obtemos o resultado na função de retorno de chamada subscribe
e usamos o `rotator 'injetado para navegar de volta para a lista de discussão.
Reagir
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
.
Em resumo: Novamente, não há muita diferença aqui. subscrever 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 }) }
Portanto, a livraria formstate Funciona assim: para cada campo do formulário, defina um FieldState
. O parâmetro passado é o valor inicial. O validators
recebe uma função, que retorna 'falso' quando o valor é válido e uma mensagem de validação quando o valor é inválido. Com as funções check' y
checkRequired`, tudo pode parecer muito declarativo.
Para ter a validação de todo o formulário, é benéfico também envolver esses campos com uma instância FormState
, que então fornece a validade adicionada.
@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}
A instância FormState
fornece propriedades value
, onChange
e error
, que pode ser facilmente usado com qualquer componente front-end.
} }
Quando form.hasError
é true
, mantemos o botão desativado. O botão Enviar envia o formulário para a mutação GraphQL apresentada acima.
Angular
No Angular, usaremos FormService
e FormBuilder
, que são partes do pacote @angular/forms
.
@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 obter mensagens de validação de ligação 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 fazer a correspondência manual das mensagens necessárias com os campos afetados. Isso não é inteiramente um inconveniente; por exemplo, ele se presta mais facilmente à internacionalização.
onSubmit({ value, valid }) { if (!valid) { return } this.formService.addPost(value) } onCancel() { this.router.navigate(['/posts']) } }
Mais uma vez, quando o formulário for 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 é referir-se ao formGroup que criamos com FormBuilder, que é a atribuição [formGroup] = 'postForm'
. Os campos do formulário estão vinculados ao modelo de formulário por meio da propriedade formControlName
. Novamente, desativamos o botão 'Enviar' quando o formulário é inválido. Devemos também adicionar o cheque sujo, porque aqui, o formulário não sujo pode ser inválido. Queremos que o estado inicial do botão seja 'habilitado'.
Em resumo: Essa abordagem para formulários em 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 links diretos, mas, por outro lado, é mais abrangente.
Oh, mais uma coisa. A produção reduziu os tamanhos dos pacotes JS, com configurações padrão para geradores de aplicativos: notavelmente Tree Shaking in React e AOT build 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.
Apenas tenha em mente que essas são 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 próxima 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 muito rapidamente.
Portanto, parece que não fomos capazes (de novo!) De dar 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 das bibliotecas com as quais escolhemos usar o React. Portanto, é principalmente uma questão de preferência pessoal.
Se você gosta de pilhas prontas, injeção de dependência poderosa e o plano para usar alguns recursos do RxJS, escolha Angular.
Se você gosta de jogar e construir sua própria pilha, como a simplicidade do JSX, e prefere propriedades computáveis mais simples, selecione React / MobX.
Mais uma vez, você pode obter o código-fonte completo do aplicativo neste artigo Aqui Y Aqui .
Ou, se preferir exemplos maiores e do RealWorld:
A programação com React / MobX é, na verdade, mais semelhante a Angular do que React / Redux. Existem algumas diferenças notáveis em modelos e tratamento de dependências, mas eles têm o mesmo paradigma mutável / vinculação de dados .
React / Redux com seu paradigma imutável / unidirecional é uma besta completamente diferente.
Não se deixe enganar pelo tamanho reduzido da biblioteca Redux. Pode ser pequeno, mas é uma estrutura de qualquer maneira. A maioria das melhores práticas do Redux hoje se concentra no uso de bibliotecas compatíveis com redux, como Redux Saga para código assíncrono e pesquisa de dados, Redux Form para gerenciamento de formulários, Selecione novamente para seletores memorizados (valores Redux calculados). Y Recompor , entre outros, para uma gestão mais precisa 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 que vimos neste post até agora.
Eu sinto que o Angular está recebendo um tratamento injusto da parte mais vocal da comunidade JavaScript. Muitas pessoas expressando insatisfação com ele provavelmente não apreciam a grande mudança que aconteceu entre o antigo AngularJS e o Angular de hoje. Na minha opinião, é um quadro muito limpo e produtivo que tomaria o mundo de assalto se tivesse surgido 1-2 anos antes.
No entanto, a Angular vem 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 é a maneira como os engenheiros do Google acham que o desenvolvimento web deve ser feito, se ainda for o caso.
Quanto ao MobX, uma avaliação semelhante se aplica. Muito legal, mas não muito apreciado.
diferença entre um s corp e ac corp
Resumindo: antes de escolher entre React e Angular, primeiro escolha seu paradigma de programação.
mutável / vinculação de dados ou imutável / unidirecional , esse parece ser o problema real.