
Partager:
Fabian a fait partie de l'équipe chargée de l'expérience des développeurs chez Vonage. C'est un ingénieur logiciel passionné qui aime l'open source, l'apprentissage automatique et le café. Lorsqu'il ne travaille pas à l'amélioration de nos documents, vous pouvez le trouver en train de faire du vélo, de lire ou d'encourager le Club Nacional de Football.
Migration des composants React vers Vue.js
Dans cet article de blog, je vais partager le parcours que nous avons suivi lorsque nous avons migré notre plateforme pour développeurs de React à Vue.js. Je vais passer en revue les raisons de ce changement, comment nous l'avons fait, et quelques leçons que nous avons apprises en cours de route.
L'Applications
La plateforme de développeurs API de Vonage est une application Ruby on Rails avec quelques composants React que nous avons utilisés de manière isolée pour gérer des cas d'utilisation très spécifiques qui impliquent beaucoup d'interactions avec l'utilisateur. Nous avons migré un total de quatre composants, qui étaient responsables d'un widget de rétroaction, de la barre de recherche, d'un compteur de caractères SMS et d'un générateur JWT (JSON Web Token). L'application est open source et vous pouvez la trouver sur Github.
La raison de cette migration était que différentes équipes au sein de l'entreprise utilisaient différents frameworks Javascript, ce qui nous empêchait non seulement de réutiliser des composants dans différentes applications, mais imposait également une barrière à l'entrée plus élevée pour les ingénieurs qui passaient d'un projet à l'autre. C'est dans cette optique que nous avons choisi Vue.js comme cadre Javascript de référence, principalement en raison de sa simplicité. Il est assez facile pour quelqu'un ayant une expérience en Javascript de construire quelque chose en quelques minutes après avoir lu les guides Vue.js.
React et Vue.js partagent certaines similitudes : ils utilisent tous deux un DOM virtuel, fournissent des composants de vue réactifs et composables, et se concentrent sur une petite bibliothèque de base, laissant le routage et la gestion globale de l'état à des bibliothèques supplémentaires. Mais ce que nous avons vraiment apprécié dans Vue.js, c'est la façon dont il s'appuie sur les technologies web classiques. Dans React, les composants expriment leur interface utilisateur à l'aide de JSX et de fonctions de rendu. Vue.js, quant à lui, traite tout HTML valide comme un modèle Vue valide, séparant la logique de la présentation (bien qu'il prenne également en charge les fonctions de rendu et JSX 😉 ).
Quelques autres caractéristiques de Vue.js nous ont séduits : la façon pratique et simple dont il gère la gestion de l'état à l'aide de data et props par rapport à React setStateLa façon dont Vue.js suit les changements et met à jour l'état d'un composant en conséquence à l'aide de la fonction données réactiveset enfin les propriétés calculées, qui vous permettent d'extraire la logique des modèles en définissant des propriétés qui dépendent d'autres propriétés.
Nous avons adopté une approche itérative. Nous avons ajouté Vue.js au projet, puis nous avons migré un composant à la fois. Heureusement, Rails est livré avec webpack et avec des intégrations de base prêtes à l'emploi pour React, Vue.js et Elm. Vous pouvez en savoir plus dans la docsmais tout ce que nous avions à faire, c'était d'exécuter :
bundle exec rails webpacker:install:vueCela a pris en charge l'installation de Vue.js et de toutes ses dépendances tout en mettant à jour les fichiers de configuration correspondants pour nous 🎉.
Tests
La première chose que nous avons réalisée est que nous n'avions pas de tests 😢. Je ne peux pas exprimer à quel point il est important d'avoir une suite de tests automatisés pour ce type de migration (ou en général d'ailleurs). L'assurance qualité manuelle prend beaucoup de temps, et aussi, qui n'aime pas l'automatisation ?
La première chose que nous avons faite a donc été d'ajouter Jest au projet, ainsi que des tests pour les différents composants. Nous nous sommes concentrés sur les tests de comportement, sur la façon dont l'interface utilisateur changeait en réponse aux interactions de l'utilisateur d'une manière indépendante du cadre, afin que nous puissions les utiliser pendant que nous réécrivions les composants. Ci-dessous, vous pouvez voir un petit exemple de l'un des tests :
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');
});Comme vous pouvez le constater, il n'y a rien de spécifique au cadre. Nous montons le composant Concatenation puis nous vérifions qu'il rend des valeurs par défaut et qu'il met à jour l'interface utilisateur après une interaction.
Pendant que nous réécrivions les composants, nous avons passé du temps à comprendre non seulement leur mise en œuvre, mais aussi la manière dont ils étaient censés fonctionner. Dans ce processus, nous avons trouvé plusieurs bugs que nous avons corrigés et pour lesquels nous avons écrit des tests. La suite de tests fait également office de documentation 🎉🎉🎉🎉, étant donné qu'elle décrit le fonctionnement des composants et la manière dont ils gèrent les différentes interactions.
Migration
Pour illustrer notre processus de migration, nous allons nous concentrer sur le composant compteur de caractères SMS. La principale fonctionnalité de ce composant est de déterminer si le texte saisi par l'utilisateur s'étendra sur plusieurs SMS en fonction de son contenu, de son encodage et de sa longueur. Vous pouvez vous référer à notre documentation si vous souhaitez en savoir plus sur la manière dont ces éléments affectent le contenu du message envoyé. Le composant se présente comme suit :
SMS character counter component
Il dispose d'un textarea avec un espace réservé où l'utilisateur peut taper/coller le contenu. Le composant indique ensuite le nombre de parties du message, sa longueur et le type d'encodage utilisé (s'il s'agit de unicode ou text).
Nous disposons d'une petite bibliothèque, CharacterCounterqui s'occupe de tout le traitement des SMS et renvoie toutes les informations nécessaires, telles que le nombre de messages requis, leur contenu, etc. Le composant Vue.js se contente donc de gérer l'interaction avec l'utilisateur, de traiter les informations et de rendre le contenu en conséquence.
Nous avons suivi les guides de style Vue.js et avons décidé d'utiliser des composants à fichier unique. et avons décidé d'utiliser des composants à fichier unique. Cela facilite la recherche et la modification des composants plutôt que d'avoir plusieurs composants définis dans un seul fichier. Le code du composant est le suivant :
<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>Tout d'abord, nous avons défini le modèle. Vous avez peut-être remarqué que nous avons utilisé quelques directives Vue.js pour le rendu conditionnelcomme v-if et v-else. C'est l'une des meilleures fonctionnalités de Vue.js que React ne propose pas. React gère le rendu conditionnel différemment, soit en utilisant l'opérateur ternaire en ligne, en ligne si avec l'opérateur logique && ou en invoquant une fonction qui renvoie un contenu différent en fonction des arguments. Voici une comparaison de la façon dont nous rendons que l'encodage est unicode dans Vue.js vs. 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>Dans les deux cas, la valeur d'une propriété a été utilisée. Dans le cas de Vue.js, les directives simplifient considérablement le rendu en ligne. Avec React, en revanche, nous avons dû créer une méthode d'aide qui renvoie les différents contenus en fonction de la propriété qui lui est transmise, ce qui a entraîné non seulement plus de code, mais aussi une division du balisage entre la fonction et la méthode d'aide. render et les méthodes d'aide.
La migration était assez simple, étant donné que le composant conservait toutes les informations dans son état sans avoir besoin de les partager avec d'autres. Il suffisait d'implémenter quelques méthodes, propriétés calculées et conditionnelles dans le code HTML.
Le textarea est liée à une propriété de données appelée body. Les propriétés suivantes propriétés calculées suivantes ont été définies :
smsInfomessagesunicodeRequiredsmsComposition
Les propriétés calculées sont essentiellement des propriétés, à la différence qu'elles ne sont réévaluées que lorsque l'une de leurs dépendances réactives change. Ces dépendances sont les propriétés utilisées dans la définition de leur corps. Voyons un exemple :
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();
},
}Ici, smsInfo est mis en cache jusqu'à ce que la valeur de body change. Si vous devez la réévaluer à chaque fois qu'elle est invoquée, vous devriez probablement utiliser une fonction method à la place.
Une fois que nous avons eu le composant Vue.js, nous nous sommes assurés que nos tests passaient, et enfin, nous avons remplacé les composants dans notre application. Et c'est tout ! Tout le code est open source et vous pouvez le trouver sur GitHub. Nous ❤ contributions ! Si vous voulez jeter un coup d'oeil à la migration complète, vous pouvez consulter la Demande d'extraction.
Nous prévoyons de rendre tous nos composants disponibles sous forme de paquets dans un avenir proche, afin de pouvoir les partager avec vous tous !
Partager:
Fabian a fait partie de l'équipe chargée de l'expérience des développeurs chez Vonage. C'est un ingénieur logiciel passionné qui aime l'open source, l'apprentissage automatique et le café. Lorsqu'il ne travaille pas à l'amélioration de nos documents, vous pouvez le trouver en train de faire du vélo, de lire ou d'encourager le Club Nacional de Football.