https://a.storyblok.com/f/270183/88917/288e9990b7/e_migrating-to-vue-js_1200x600.png

Migração de componentes do React para o Vue.js

Publicado em November 5, 2020

Tempo de leitura: 5 minutos

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:vue

Isso 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 componentSMS 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>&nbsp;</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:

  • smsInfo

  • messages

  • unicodeRequired

  • smsComposition

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:

https://a.storyblok.com/f/270183/384x384/d4e395e293/fabianrodiguez.png
Fabian RodriguezEx-funcionários da Vonage

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.