
Compartir:
Fabian formó parte del equipo de experiencia de desarrolladores de Vonage. Es un ingeniero de software apasionado al que le encanta el código abierto, el aprendizaje automático y el café. Cuando no está trabajando en mejorar nuestros documentos, se le puede encontrar haciendo ciclismo, leyendo o animando al Club Nacional de Fútbol.
Migración de componentes React a Vue.js
En esta entrada del blog, voy a compartir el viaje por el que pasamos cuando migramos nuestra Plataforma para desarrolladores desde React a Vue.js. Voy a repasar las razones detrás del cambio, cómo lo hicimos, y algunas lecciones que aprendimos en el camino.
La aplicación
La plataforma para desarrolladores de API de Vonage es una aplicación Ruby on Rails con algunos componentes React que utilizamos de forma aislada para gestionar casos de uso muy específicos que implican mucha interacción con el usuario. Migramos un total de cuatro componentes, que eran responsables de un widget de comentarios, la barra de búsqueda, un contador de caracteres SMS y un generador de JWT (JSON Web Token). La aplicación es de código abierto y está disponible en Github.
El motivo de la migración era que los distintos equipos de la empresa utilizaban diferentes marcos de Javascript, lo que no sólo nos impedía reutilizar componentes en distintas aplicaciones, sino que también suponía una barrera de entrada más alta para los ingenieros que cambiaban de un proyecto a otro. Teniendo esto en cuenta, elegimos Vue.js como nuestro framework Javascript preferido principalmente por su simplicidad. Es bastante fácil para alguien con experiencia en Javascript construir algo en cuestión de minutos después de leer las guías de Vue.js.
React y Vue.js comparten algunas similitudes: ambos utilizan un DOM virtual, proporcionan componentes de vista reactivos y componibles, y se centran en una pequeña biblioteca central, dejando el enrutamiento y la gestión global del estado a bibliotecas adicionales. Pero lo que realmente nos gustó de Vue.js es cómo se basa en las tecnologías web clásicas. En React, los componentes expresan su UI usando JSX y funciones de renderizado. Vue.js, por otro lado, trata cualquier HTML válido como una plantilla Vue válida, separando la lógica de la presentación (aunque también soportan funciones render y JSX 😉).
Hay algunas otras características de Vue.js que lo hicieron atractivo para nosotros: la forma conveniente y sencilla en que maneja la gestión de estados usando data y props en comparación con React setStatecómo Vue.js rastrea los cambios y actualiza el estado de un componente usando datos reactivosy, por último, las propiedades computadas, que permiten extraer lógica de las plantillas definiendo propiedades que dependen de otras propiedades.
El enfoque que adoptamos fue iterativo. Añadimos Vue.js al proyecto y luego migramos un componente cada vez. Afortunadamente, Rails viene con webpack y con integraciones básicas para React, Vue.js y Elm. Puedes leer más sobre esto en la documentación docspero lo único que tuvimos que hacer fue ejecutar
bundle exec rails webpacker:install:vueEso se encargó de instalar Vue.js y todas sus dependencias a la vez que nos actualizaba los archivos de configuración correspondientes 🎉.
Pruebas
Lo primero que nos dimos cuenta es que no teníamos ninguna prueba 😢. No puedo expresar lo importante que es tener un conjunto de pruebas automatizadas para este tipo de migración (o en general). El QA manual lleva mucho tiempo, y además, ¿a quién no le gusta la automatización?
Así que lo primero que hicimos fue añadir Jest al proyecto, junto con pruebas para los distintos componentes. Nos centramos en probar el comportamiento, cómo cambiaba la interfaz de usuario en respuesta a las interacciones del usuario de una forma independiente del marco de trabajo, para poder utilizarlas mientras reescribíamos los componentes. A continuación, puedes ver un pequeño ejemplo de una de las pruebas:
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 puedes ver, no hay nada específico del marco. Montamos el componente Concatenation y comprobamos que muestra algunos valores por defecto y actualiza la interfaz de usuario después de una interacción.
Mientras reescribíamos los componentes, dedicamos tiempo no sólo a entender su implementación, sino también cómo se suponía que debían funcionar. En este proceso, encontramos varios errores que corregimos y para los que escribimos pruebas. El conjunto de pruebas también actúa como documentación 🎉🎉🎉, ya que describe cómo funcionan los componentes y cómo gestionan las distintas interacciones.
Migración
Para ilustrar nuestro proceso de migración, nos centraremos en el componente contador de caracteres SMS. La funcionalidad principal de este componente es saber si el texto introducido por el usuario se extenderá en varios mensajes SMS en función de su contenido, codificación y longitud. Puedes consultar nuestra documentación si quieres saber más sobre cómo afectan estas cosas a lo que se envía. El componente tiene este aspecto:
SMS character counter component
Dispone de un textarea con un marcador de posición donde el usuario puede escribir/pegar el contenido,. A continuación, el componente le indicará en cuántas partes se dividirá el mensaje, su longitud y el tipo de codificación utilizado (si es unicode o text).
Disponemos de una pequeña librería CharacterCounterque se encarga de todo el procesamiento de SMS y devuelve toda la información necesaria, como el número de mensajes necesarios, su contenido, etc. Así, el componente Vue.js sólo se encarga de la interacción con el usuario, procesa la información y renderiza el contenido en consecuencia.
Hemos seguido las Guías de estilo y decidimos utilizar componentes de un solo archivo. Esto hace que sea más fácil encontrar y editar componentes en lugar de tener múltiples componentes definidos en un archivo. El código del componente es el siguiente:
<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>En primer lugar, definimos la plantilla. Habrás notado que usamos algunas directivas de Vue.js para el renderizado condicionalcomo v-if y v-else. Esta es una de las mejores características de Vue.js que React no proporciona. React maneja renderizado condicional de forma diferente, ya sea usando el operador ternario inline, inline if con el operador lógico && o invocando una función que devuelve un contenido diferente en función de los argumentos. A continuación se muestra una comparación de cómo renderizamos que la codificación es unicode en Vue.js frente a 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>En ambos casos, se utilizó el valor de una propiedad. En el caso de Vue.js, las directivas hacen que sea bastante sencillo renderizar todo inline. Con React, por otro lado, tuvimos que crear un método helper que devuelve el contenido diferente basado en la propiedad que se le pasa, lo que llevó no sólo a más código, sino también a tener el marcado dividido entre la función y los métodos helper. render y los métodos de ayuda.
La migración fue bastante sencilla, dado que el componente mantenía toda la información en su estado sin necesidad de compartirla con otros. Todo lo que se necesitaba era implementar algunos métodos, propiedades computadas y condicionales en el HTML.
La dirección textarea está vinculada a una propiedad de datos denominada body. Las siguientes propiedades calculadas definidas:
smsInfomessagesunicodeRequiredsmsComposition
Las propiedades computadas son esencialmente propiedades, con la diferencia de que sólo se reevalúan cuando una de sus dependencias reactivas cambia. Estas dependencias son las propiedades utilizadas dentro de la definición de su cuerpo. Veamos un ejemplo:
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();
},
}Aquí, smsInfo se almacena en caché hasta que cambie el valor de body cambie. Si necesita reevaluarlo cada vez que se invoca, entonces es probable que desee utilizar un método method en su lugar.
Una vez que tuvimos el componente Vue.js, nos aseguramos de que nuestras pruebas pasaban, y finalmente, reemplazamos los componentes en nuestra aplicación. Y ya está. Todo el código es de código abierto y lo puedes encontrar en GitHub. Nosotros ❤ ¡contribuciones! Si quieres echar un vistazo a la migración completa, puedes consultar el correspondiente Pull Request.
En un futuro próximo, todos nuestros componentes estarán disponibles en paquetes para que podamos compartirlos con todos ustedes.
Compartir:
Fabian formó parte del equipo de experiencia de desarrolladores de Vonage. Es un ingeniero de software apasionado al que le encanta el código abierto, el aprendizaje automático y el café. Cuando no está trabajando en mejorar nuestros documentos, se le puede encontrar haciendo ciclismo, leyendo o animando al Club Nacional de Fútbol.