Motifs élégants en JavaScript moderne (cycle d'équipe Bill Sourour)

Bonjour, Habr! Bill Sourour, un professeur JavaScript assez connu à l'époque, a écrit plusieurs articles sur les modèles modernes dans JS. Dans le cadre de cet article, nous allons essayer de revoir les idées qu'il a partagées. Non pas qu'il s'agissait de quelques modèles uniques, mais j'espère que l'article trouvera son lecteur. Cet article n’est pas une "traduction" du point de vue de la politique de Habr puisque Je décris mes pensées sur lesquelles les articles de Bill m'ont pointé.

Rooro


L'abréviation signifie Recevoir un objet, retourner un objet - obtenir un objet, retourner un objet. Je fournis un lien vers l'article d'origine: lien

Bill a écrit qu'il avait trouvé un moyen d'écrire des fonctions dans lesquelles la plupart d'entre elles n'acceptent qu'un seul paramètre - un objet avec des arguments de fonction. Ils renvoient également un objet de résultats. Bill a été inspiré par la restructuration de cette idée (l'une des fonctionnalités d'ES6).

Pour ceux qui ne connaissent pas la déstructuration, je donnerai les explications nécessaires au cours de l'histoire.

Imaginez que nous ayons des données utilisateur contenant ses droits sur certaines sections de l'application présentées dans l'objet de données. Nous devons montrer certaines informations basées sur ces données. Pour ce faire, nous pourrions proposer l'implémentation suivante:

//   const user = { name: 'John Doe', login: 'john_doe', password: 12345, active: true, rules: { finance: true, analitics: true, hr: false } }; //   const users = [user]; //,     function findUsersByRule ( rule, withContactInfo, includeInactive) { //        active const filtredUsers= users.filter(item => includeInactive ? item.rules[rule] : item.active && item.rules[rule]); //  ()   ( )     withContactInfo return withContactInfo ? filtredUsers.reduce((acc, curr) => { acc[curr.id] = curr; return acc; }, {}) : filtredUsers.map(item => item.id) } //     findUsersByRule( 'finance', true, true) 

En utilisant le code ci-dessus, nous atteindrions le résultat souhaité. Cependant, l'écriture de code de cette façon comporte plusieurs écueils.

Tout d'abord, l'appel à la fonction findUsersByRule très douteux. Remarquez à quel point les deux derniers paramètres sont ambigus. Que se passe-t-il si notre application n'a presque jamais besoin d'informations de contact (avecContactInfo) mais a presque toujours besoin d'utilisateurs inactifs (includeInactive)? Nous devrons toujours transmettre des valeurs logiques. Maintenant, alors que la déclaration de fonction est à côté de son appel, ce n'est pas si effrayant, mais imaginez que vous voyez un tel appel quelque part dans un autre module. Vous devrez rechercher un module avec une déclaration de fonction afin de comprendre pourquoi deux valeurs logiques sous forme pure lui sont transférées.

Deuxièmement, si nous voulons rendre certains paramètres obligatoires, nous devrons écrire quelque chose comme ceci:

 function findUsersByRule ( role, withContactInfo, includeInactive) { if (!role) { throw Error(...) ; } //...  } 

Dans ce cas, notre fonction, en plus de ses responsabilités de recherche, effectuera également la validation, et nous voulions simplement trouver des utilisateurs par certains paramètres. Bien sûr, la fonction de recherche peut prendre des fonctions de validation, mais la liste des paramètres d'entrée s'étendra. C'est également un inconvénient d'un tel schéma de codage.

La déstructuration consiste à décomposer une structure complexe en parties simples. En JavaScript, une telle structure complexe est généralement un objet ou un tableau. À l'aide de la syntaxe de déstructuration, vous pouvez extraire de petits fragments de tableaux ou d'objets. Cette syntaxe peut être utilisée pour déclarer des variables ou leur fonction. Vous pouvez également gérer les structures imbriquées en utilisant déjà la syntaxe de la déstructuration imbriquée.

En utilisant la déstructuration, la fonction de notre exemple précédent ressemblera à ceci:

 function findUsersByRule ({ rule, withContactInfo, includeInactive}) { //    } findUsersByRule({ rule: 'finance', withContactInfo: true, includeInactive: true}) 

Veuillez noter que notre fonction semble presque identique, sauf que nous mettons des crochets autour de nos paramètres. Au lieu de recevoir trois paramètres différents, notre fonction attend désormais un seul objet avec des propriétés: rule , withContactInfo et includeInactive .

C'est beaucoup moins ambigu, beaucoup plus facile à lire et à comprendre. De plus, le saut ou un autre ordre de nos paramètres n'est plus un problème, car maintenant ils sont nommés propriétés de l'objet. Nous pouvons également ajouter en toute sécurité de nouveaux paramètres à la déclaration de fonction. De plus, depuis Comme la déstructuration copie la valeur transmise, ses modifications dans la fonction n'affecteront pas l'original.

Le problème avec les paramètres requis peut également être résolu de manière plus élégante.

 function requiredParam (param) { const requiredParamError = new Error( `Required parameter, "${param}" is missing.` ) } function findUsersByRule ({ rule = requiredParam('rule'), withContactInfo, includeInactive} = {}) {...} 

Si nous ne transmettons pas la valeur de la règle, la fonction transmise par défaut fonctionnera, ce qui lèvera une exception.

Les fonctions de JS ne peuvent renvoyer qu'une seule valeur, vous pouvez donc utiliser un objet pour transférer plus d'informations. Bien sûr, nous n'avons pas toujours besoin de la fonction pour renvoyer beaucoup d'informations, dans certains cas, nous serons satisfaits du retour de la primitive, par exemple, findUserId retournera tout naturellement un identifiant par une condition.

De plus, cette approche simplifie la composition des fonctions. En effet, avec la composition, les fonctions ne doivent prendre qu'un seul paramètre. Le modèle RORO adhère au même contrat.

Bill Sourour: «Comme tout modèle, RORO devrait être considéré comme un autre outil dans notre boîte à outils. "Nous l'utilisons là où il en profite, rendant la liste des paramètres plus compréhensible et flexible, et la valeur de retour plus expressive."

Usine de glace


Vous pouvez trouver l'article original sur ce lien.

Selon l'auteur, ce modèle est une fonction qui crée et renvoie un objet figé.

Pense Bill. que dans certaines situations, ce modèle peut remplacer les classes ES6 habituelles pour nous. Par exemple, nous avons un certain panier alimentaire dans lequel nous pouvons ajouter / supprimer des produits.

Classe ES6:

 // ShoppingCart.js class ShoppingCart { constructor({db}) { this.db = db } addProduct (product) { this.db.push(product) } empty () { this.db = [] } get products () { return Object .freeze([...this.db]) } removeProduct (id) { // remove a product } // other methods } // someOtherModule.js const db = [] const cart = new ShoppingCart({db}) cart.addProduct({ name: 'foo', price: 9.99 }) 

Les objets créés à l'aide du new mot-clé sont mutables, c'est-à-dire nous pouvons remplacer la méthode d'instance de classe.

 const db = [] const cart = new ShoppingCart({db}) cart.addProduct = () => 'nope!' //   JS  cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!"     

Il faut également se rappeler que les classes dans JS sont implémentées sur la délégation de prototype, par conséquent, nous pouvons changer l'implémentation de la méthode dans le prototype de la classe et ces changements affecteront toutes les instances existantes (j'en ai parlé plus en détail dans l' article sur la POO ).

 const cart = new ShoppingCart({db: []}) const other = new ShoppingCart({db: []}) ShoppingCart.prototype .addProduct = () => 'nope!' //     JS cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!" other.addProduct({ name: 'bar', price: 8.88 }) // output: "nope!" 

D'accord, de telles fonctionnalités peuvent nous causer beaucoup de problèmes.

Un autre problème courant consiste à affecter une méthode d'instance à un gestionnaire d'événements.

 document .querySelector('#empty') .addEventListener( 'click', cart.empty ) 

Cliquer sur le bouton ne videra pas le panier. La méthode attribue une nouvelle propriété à notre bouton nommé db et définit cette propriété sur [] au lieu d'affecter la base de données de l'objet panier. Cependant, il n'y a pas d'erreur dans la console, et votre bon sens vous dira que le code devrait fonctionner, mais ce n'est pas le cas.

Pour faire fonctionner ce code, vous devez écrire une fonction flèche:

 document .querySelector("#empty") .addEventListener( "click", () => cart.empty() ) 

Ou liez le contexte avec une liaison:

 document .querySelector("#empty") .addEventListener( "click", cart.empty.bind(cart) ) 

La fabrique de glace nous aidera à éviter ces pièges.

 function makeShoppingCart({ db }) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, // others }) function addProduct (product) { db.push(product) } function empty () { db = [] } function getProducts () { return Object .freeze([...db]) } function removeProduct (id) { // remove a product } // other functions } // someOtherModule.js const db = [] const cart = makeShoppingCart({ db }) cart.addProduct({ name: 'foo', price: 9.99 }) 

Caractéristiques de ce modèle:

  • pas besoin d'utiliser le nouveau mot-clé
  • pas besoin de lier ça
  • le chariot est entièrement portable
  • des variables locales peuvent être déclarées qui ne seront pas visibles de l'extérieur

 function makeThing(spec) { const secret = 'shhh!' return Object.freeze({ doStuff }) function doStuff () { //    secret } } // secret    const thing = makeThing() thing.secret // undefined 

  • le modèle prend en charge l'héritage
  • la création d'objets à l'aide d'Ice Factory est plus lente et prend plus de mémoire que l'utilisation d'une classe (dans de nombreuses situations, nous pouvons avoir besoin de classes, donc je recommande cet article )
  • il s'agit d'une fonction courante qui peut être affectée en tant que rappel

Conclusion


Lorsque nous parlons de l'architecture du logiciel en cours de développement, nous devons toujours faire des compromis pratiques. Il n'y a pas de règles et de restrictions strictes dans ce chemin, chaque situation est unique, donc plus il y a de modèles dans notre arsenal, plus il est probable que nous choisirons la meilleure option d'architecture dans une situation particulière.

Source: https://habr.com/ru/post/fr472816/


All Articles