
Compartilhar:
Fabian fazia parte da Equipe de Experiência do Desenvolvedor da Vonage. Ele é um engenheiro de software apaixonado que adora código aberto, aprendizado de máquina e café. Quando não está trabalhando para melhorar nossa documentação, você pode encontrá-lo andando de bicicleta, lendo ou torcendo pelo Club Nacional de Football.
Migração de componentes do React para o Vue.js
Nesta postagem do blog, vou compartilhar a jornada pela qual passamos ao migrar nossa Plataforma de Desenvolvedores do React para Vue.js. Vou explicar os motivos por trás dessa mudança, como fizemos isso e algumas lições que aprendemos ao longo do caminho.
The Applications
A Plataforma de Desenvolvedores da API da Vonage é um aplicativo em Ruby on Rails com alguns componentes React que utilizamos isoladamente para lidar com casos de uso muito específicos que envolvem muita interação com o usuário. Migramos um total de quatro componentes, responsáveis por um widget de feedback, a barra de pesquisa, um contador de caracteres de SMS e um gerador de JWT (JSON Web Token). O aplicativo é de código aberto e você pode encontrá-lo no GitHub.
O motivo por trás da migração foi que diferentes equipes dentro da empresa estavam usando diferentes frameworks de JavaScript, o que não só nos impedia de reutilizar componentes entre diferentes Applications, mas também criava uma barreira maior para os engenheiros que alternavam entre projetos. Com isso em mente, escolhemos o Vue.js como nosso framework de JavaScript preferido, principalmente por causa de sua simplicidade. É bastante fácil para alguém com experiência em JavaScript criar algo em poucos minutos após ler os guias do Vue.js.
O React e o Vue.js compartilham algumas semelhanças: ambos utilizam um DOM virtual, oferecem componentes de visualização reativos e combináveis e se concentram em uma biblioteca central compacta, deixando o roteamento e o gerenciamento do estado global a cargo de bibliotecas adicionais. Mas o que realmente nos agradou no Vue.js foi a forma como ele se baseia em tecnologias web clássicas. No React, os componentes expressam sua interface do usuário usando JSX e funções de renderização. O Vue.js, por outro lado, trata qualquer HTML válido como um modelo válido do Vue, separando a lógica da apresentação (embora também ofereça suporte a funções de renderização e JSX 😉).
Existem alguns outros recursos do Vue.js que o tornaram atraente para nós: a maneira prática e simples como ele lida com o gerenciamento de estado usando data e props em comparação com o React setState, e como o Vue.js rastreia alterações e atualiza o estado de um componente de acordo com elas usando dados reativose, finalmente, as propriedades computadas, que permitem extrair a lógica dos modelos ao definir propriedades que dependem de outras propriedades.
A abordagem que adotamos foi iterativa. Adicionamos o Vue.js ao projeto e, em seguida, migramos um componente por vez. Felizmente, o Rails já vem com o webpack e com integrações básicas prontas para uso com React, Vue.js e Elm. Você pode ler mais sobre isso nos documentação, mas tudo o que precisamos fazer foi executar:
bundle exec rails webpacker:install:vueIsso resolveu a instalação do Vue.js e de todas as suas dependências, além de atualizar os arquivos de configuração correspondentes para nós 🎉.
Testes
A primeira coisa que percebemos foi que não tínhamos nenhum teste 😢. Não consigo expressar o quanto é importante ter um conjunto de testes automatizados para esse tipo de migração (ou, aliás, em geral). O controle de qualidade manual leva muito tempo e, além disso, quem não gosta de automação?
Então, a primeira coisa que fizemos foi adicionar o Jest ao projeto, juntamente com testes para os diferentes componentes. Nos concentramos em testar o comportamento, ou seja, como a interface do usuário mudava em resposta às interações do usuário, de uma forma independente de framework, para que pudéssemos usá-los enquanto reescrevíamos os componentes. Abaixo, você pode ver um pequeno exemplo de um dos testes:
describe('Concatenation', function() {
describe('Initial rendering', function() {
it('Renders the default message', async function() {
const wrapper = shallowMount(Concatenation);
expect(wrapper.find('h2').text()).toEqual('Try it out');
expect(wrapper.html()).toContain('<h4>Message</h4>');
expect(wrapper.find('textarea').element.value).toEqual(
"It was the best of times, it was the worst of times, it was the age of wisdom..."
);
it('notifies the user if unicode is required and updates the UI accordingly', function() {
const wrapper = shallowMount(Concatenation);
wrapper.find('textarea').setValue('😀');
expect(wrapper.find('i.color--success').exists()).toBeTruthy();
expect(wrapper.find('#sms-composition').text()).toEqual('2 characters sent in 1 message part');
expect(wrapper.find('code').text()).toContain('😀');
wrapper.find('textarea').setValue('not unicode');
expect(wrapper.find('i.color--error').exists()).toBeTruthy();
expect(wrapper.find('#sms-composition').text()).toEqual('11 characters sent in 1 message part');
expect(wrapper.find('code').text()).toContain('not unicode');
});Como você pode ver, não há nada específico do framework. Nós montamos o Concatenation componente e, em seguida, verificamos se ele exibe alguns valores padrão e atualiza a interface do usuário após uma interação.
Enquanto reescrevíamos os componentes, dedicamos tempo não apenas a compreender sua implementação, mas também a entender como eles deveriam funcionar. Nesse processo, encontramos vários bugs que corrigimos e para os quais escrevemos testes. O conjunto de testes também funciona como documentação 🎉🎉🎉, já que descreve como os componentes funcionam e como lidam com diferentes interações.
Migração
Para ilustrar nosso processo de migração, vamos nos concentrar no componente contador de caracteres de SMS. A principal funcionalidade desse componente é determinar se o texto digitado pelo usuário será dividido em várias mensagens de SMS com base em seu conteúdo, codificação e comprimento. Você pode consultar nossos documentação se quiser saber mais sobre como esses fatores afetam o que é enviado. O componente tem a seguinte aparência:
SMS character counter component
Ele possui um textarea um espaço reservado onde o usuário pode digitar ou colar o conteúdo. Em seguida, o componente informará em quantas partes a mensagem será dividida, seu comprimento e o tipo de codificação utilizado (seja unicode ou text).
Temos uma pequena biblioteca, CharacterCounterque cuida de todo o processamento de SMS e retorna todas as informações necessárias, como o número de mensagens necessárias, seu conteúdo etc. Assim, o componente Vue.js se encarrega apenas da interação com o usuário, processa as informações e exibe o conteúdo de acordo com elas.
Seguimos os guias de estilo do Vue.js Guias de Estilo e decidimos usar componentes de arquivo único. Isso facilita a localização e a edição dos componentes, em vez de ter vários componentes definidos em um único arquivo. O código do componente é o seguinte:
<template>
<div class="Vlt-box">
<h2>Try it out</h2>
<h4>Message</h4>
<div class="Vlt-textarea">
<textarea v-model="body" />
</div>
<div class="Vlt-margin--top2" />
<h4>Data</h4>
<div class="Vlt-box Vlt-box--white Vlt-box--lesspadding">
<div class="Vlt-grid">
<div class="Vlt-col Vlt-col--1of3">
<b>Unicode is Required?</b>
<i v-if="unicodeRequired" class="icon icon--large icon-check-circle color--success"></i>
<i v-else class="icon icon--large icon-times-circle color--error"></i>
</div>
<div class="Vlt-col Vlt-col--2of3">
</div>
<hr class="hr--shorter"/>
<div class="Vlt-col Vlt-col--1of3">
<b>Length</b>
</div>
<div class="Vlt-col Vlt-col--2of3" v-html="smsComposition" id="sms-composition"></div>
</div>
</div>
<h4>Parts</h4>
<div class="Vlt-box Vlt-box--white Vlt-box--lesspadding" id="parts">
<div v-for= "(message, index) in messages" class="Vlt-grid">
<div class="Vlt-col Vlt-col--1of3"><b>Part {{index + 1}}</b></div>
<div class="Vlt-col Vlt-col--2of3">
<code>
<span v-if="messages.length > 1">
<span class="Vlt-badge Vlt-badge--blue">User Defined Header</span>
<span> </span>
</span>
{{message}}
</code>
</div>
<hr v-if="index + 1 !== messages.length" class="hr--shorter"/>
</div>
</div>
</div>
</template>
<script>
import CharacterCounter from './character_counter';
export default {
data: function () {
return {
body: 'It was the best of times, it was the worst of times, it was the age of wisdom...
};
},
computed: {
smsInfo: function() {
return new CharacterCounter(this.body).getInfo();
},
messages: function() {
return this.smsInfo.messages;
},
unicodeRequired: function() {
return this.smsInfo.unicodeRequired;
},
smsComposition: function() {
let count = this.smsInfo.charactersCount;
let characters = this.pluralize('character', count);
let messagesLength = this.messages.length;
let parts = this.pluralize('part', messagesLength);
return `${count} ${characters} sent in ${messagesLength} message ${parts}`;
}
},
methods: {
pluralize: function(singular, count) {
if (count === 1) { return singular; }
return `${singular}s`;
}
}
}
</script>
<style scoped>
textarea {
width: 100%;
height: 150px;
resize: vertical;
}
code {
whiteSpace: normal;
wordBreak: break-all;
}
</style>Primeiro, definimos o modelo. Você deve ter notado que usamos algumas diretivas do Vue.js para renderização condicional, como v-if e v-else. Esse é um dos melhores recursos do Vue.js que o React não oferece. O React lida com a renderização condicional de maneira diferente, seja usando o operador ternário inline, o `if` inline com o operador lógico && ou chamando uma função que retorna conteúdo diferente com base nos argumentos. Abaixo está uma comparação de como renderizamos que a codificação é unicode no Vue.js e no React:
// Vue.js
<div class="Vlt-col Vlt-col--1of3">
<b>Unicode is Required?</b>
<i v-if="unicodeRequired" class="icon icon--large icon-check-circle color--success"></i>
<i v-else class="icon icon--large icon-times-circle color--error"></i>
</div> // React
renderUtfIcon(required) {
if (required) {
return (<i className="icon icon--large icon-check-circle color--success"/>)
} else {
return (<i className="icon icon--large icon-times-circle color--error"/>)
}
}
<div className="Vlt-col Vlt-col--1of3">
<b>Unicode is Required?</b>
{ this.renderUtfIcon(smsInfo.unicodeRequired) }
</div>Em ambos os casos, foi utilizado o valor de uma propriedade. No caso do Vue.js, as diretivas facilitam bastante a renderização de tudo inline. Já no React, por outro lado, tivemos que criar um método auxiliar que retorna o conteúdo diferente com base na propriedade passada a ele, o que resultou não apenas em mais código, mas também na divisão da marcação entre a render função e métodos auxiliares.
A migração foi bastante simples, já que o componente mantinha todas as informações em seu próprio estado, sem a necessidade de compartilhá-las com outros. Bastou implementar alguns métodos, propriedades calculadas e condições no HTML.
O textarea está vinculado a uma propriedade de dados chamada body. As seguintes propriedades calculadas foram definidas:
smsInfomessagesunicodeRequiredsmsComposition
Propriedades calculadas são, essencialmente, propriedades, com a diferença de que só são reavaliadas quando uma de suas dependências reativas mudar. Essas dependências são as propriedades utilizadas dentro de sua definição. Vejamos um exemplo:
data: function () {
return {
body: 'It was the best of times, it was the worst of times, it was the age of wisdom...'
};
},
computed: {
smsInfo: function() {
return new CharacterCounter(this.body).getInfo();
},
}Aqui, smsInfo é armazenado em cache até que o valor de body mudar. Se você precisar reavaliá-lo toda vez que for chamado, provavelmente será melhor usar um method em vez disso.
Depois de criarmos o componente Vue.js, verificamos se nossos testes estavam sendo aprovados e, por fim, substituímos os componentes em nosso aplicativo. E pronto! Todo o código é de código aberto e você pode encontrá-lo no GitHub. ❤ contribuições! Se você quiser dar uma olhada na migração completa, pode conferir o Pull Request.
Estamos planejando disponibilizar todos os nossos componentes como pacotes em breve, para que possamos compartilhá-los com todos vocês!
Compartilhar:
Fabian fazia parte da Equipe de Experiência do Desenvolvedor da Vonage. Ele é um engenheiro de software apaixonado que adora código aberto, aprendizado de máquina e café. Quando não está trabalhando para melhorar nossa documentação, você pode encontrá-lo andando de bicicleta, lendo ou torcendo pelo Club Nacional de Football.