Nouveautés du CanJS 2.3

canjs
Standard

Aujourd’hui, nous annonçons la sortie du CanJS 2.3, ici le téléchargement du 2.3.11, sa page npm, et son changelog. Cette version contient plus de 800 commits, quelques corrections de bugs, améliorations des performances et des nouvelles fonctionnalités qui prouvent la force expressive du framework.

Cet article montre les nouvelles fonctionnalités les plus importantes du 2.3:

  • {{#each}} diffing: modification minimal du DOM si #each est passé par des différentes instances de la liste
  • Les appels aux expressions (Call expressions) : Appeler les fonctions dans les templates stache comme: {{capitalize( pluralize('person',2) )}}
  • Syntaxe du binding : Contrôler la direction du binding des données et écouter les événements du viewModel dans stache.
  • Les opérateurs clef: Passer les fonctions dans stache où utilise les variables du template
  • Améliration des performances : Des computes et live binding plus rapide

{{#each}} diffing

Avec l’introduction du can.compute dans CanJS 1.1 et le plugin define dans CanJS 2.1, une des meilleurs fonctionnalités dans CanJS été sa capacité de dérivé une nouvelle valeur à partir d’autres valeurs. Ce pattern s’est emparé dans la communauté React pour des bonnes raisons, ce type d’applications est comme une équation mathématique.

Par exemple, dans TodoMVC, les todos affichées sont dérivées de todos chargé à partir du serveur et la valeur filter du route :

displayList: function () {
    var filter = route.attr('filter');
    return this.attr('todos').filter(function (todo) {
        if (filter === 'completed') {
            return todo.attr('complete');
        }
        else if (filter === 'active') {
            return !todo.attr('complete');
        }
        else {
            return true;
        }
    });
}

les todos retournées par displayList sont converties en une liste d’éléments <li> avec {{#each}} :

{{#each displayList}}
   <ul>
  <li>...</li>
   </ul>
{{/each}}

avant la version 2.3, quand une nouvelle todo est créée, displayList serait recalculée et chaque <li> serait supprimée et re-créée.

Avec la version 2.3 {{#each}} exécute un diff du nouvelle liste et l’ancien liste, et rajoute seulement une seule <li> pour la nouvelle todo.

Vous pouvez examiner la différence du performance ente la version 2.2 sans le diff et la version 2.3 avec le diff en ajoutant une nouvelle todo à la liste de 1000 todo :

Demo du 2.2

JS Bin on jsbin.com

Demo du 2.3

JS Bin on jsbin.com

Avec la version 2.3 vous pouvez profiter pleinement du capacité du CanJS à dériver des valeurs à partir d’autres valeurs sans se préoccuper à propos des performances. Dans les prochaines versions, avec l’aide de can-derive, nous serons capable de le rendre encore plus rapide ( à partir diff linéaire à un insert d’arbre binaire logrithmique)

Les appel aux expressions (Call expressions)

CanJS 2.3 ajoute les appels aux expressions. Similaire à des appels à des fonctions javascript normales. Par exemple :

{{pluralize(type, $ages.length)}}

Les appels aux expressions ont trois (3) avantages par rapport au fonctions normales des helpers :

  • Ils passent des valeurs au lieu de computes
  • Ils peuvent être imbriqués
  • Ils peuvent facilement comprendre les règles de recherche de portée

Les appels aux expressions passent des valeurs au lieu de computes

Par défault, les helpers de stache et mustache retournent des computes qui représentent an argument observable. Par exemple, le helper pluralize dans le fiddle suivant a besoin d’avoir une valeur des arguments compute type() et count():
JS Bin on jsbin.com

Passer des computes été une ancienne décision that qui été confuse. Heureusement, les appels des expressions sont là pour simplifier les choses. Ils passent la valeur d’un argument observable. Dans l’exemple suivant, remarquez comment pluralize simplement utilise les valeurs type et count :

JS Bin on jsbin.com

Les expressions imbriquées

Les appels aux expressions peuvent être imbriquées en tant que partie d’ autres appel aux expressions où autres expressions helper comme :


{{capitalize(pluralize(type,args.length)}}

{{#each stateForTeam(teamId.id)}}

cela facilite la compositions des comportements. L’exemple suivant statsForTeam est capable d’avoir une liste des scores d’une équipe. En utilisant le diffing de #each, si la liste des scores change, la liste des scores se met à jour avec un changement minimal du DOM. Remarquez que la couleur jaune reste a ça place quand les stats sont ajoutés :
JS Bin on jsbin.com

Les règles de recherche de portée (Scope)

Les fonctions des appels aux expressions sont trouvé premièrement dans le Scope et seulement si rien n’est trouvé, la portée du HelperOptions est cherché.Contrairement aux expressions des fonctions des assistants (helpers) qui trouve les méthodes dans l’ordre suivant :

  • Chercher dans le context actuel
  • Chercher dans l’assistant (helper) du scope
  • Chercher dans le scope

remarquez que l’appel à l’expression pluralize appel la méthode pluralize dans le scope:
JS Bin on jsbin.com

Les appels aux expressions rendent les appels des fonctions à partir de stache plus simple.

Binding Syntaxes

CanJS 2.3 supporte une nouvelle syntaxe du binding qui :

  • Permet un comportement du binding plus fin
  • supporte les bindings dynamique

cette section explique les nouvelles syntaxe du « binding » et comment atteindre ces objectifs. Premièrement on va faire un petit rappel sur la syntaxe précédente du bindings dans CanJS.
CanJS 2.2 supporte deux (2) type de syntaxes pour les bindings :

  • Binding des événements : utilisé pour écouter les événements et appeler une méthode dans le scope
  • Data binding : utilisé pour lier une valeur dans scope avec soit une valeur dans le viewModel d’un composant où une propertyattribute de l’élément DOM
Type Exemple
Événements DOM
<my-comp can-click="scopeMethod"/>
Data – 2 way

scope to viewModel

<my-comp vm-prop="{scopeProp}"/>
Data – 2 way

scope to DOM

<input can-value="{scopeProp}"/>

Les syntaxes du 2.2 a plusieurs problémes:

Primo, sont incompatible avec les bindings dynamiques. Les bindings dynamiques sont des comportements de binding qui changent quand les attributs sont ajoutés et supprimés d’un composant, par exemple :

  <my-component vm-prop="{ {{nomClef}} }"{{/value}}/>
  <my-component {{#if value}}vm-prop="{clef}"{{/value}}/>

C’est possible que quelqu’un veut utiliser les tags magiques pour côntroler dynamiquement les bindings appliqués sur un composant. La syntaxe 2.2 rend la tache difficile où impossible.

Secundo, les two-way bindings sont utiles pour établir une communication entre les composants, des fois ils peuvent rendre le debugging plus difficile. Par exemple, un composant parent peut passer une valeur à un composant fils, mais ils se met pas à jour quand le fils change cette valeur.

Un scénario fréquent peut être un autocomplete qui doit seulement récupérer des suggestions après que l’utilisateur a entré plus de deux (2) caractères.Pour faciliter une situation pareil, notre nouvelles syntaxes permet plus de flexibilité pour controller le comportement des bindings.

Nouvelles syntaxes

Dans 2.3, les bindigns utilisent les régles de syntaxe suivantes :

  • {gauche}="droite": mettre à jour  » gauche  » avec la valeur de droite
  • (gauche)="droite": écoute les changements de gauche, quand il est déclenché, éxecute droite
  • {(gauche)}="droite" : two-way bind gauche et droite
  • $: agir sur les événements, attributs ou propriétés des l ‘élément au lieu du viewModel
  • ^ : inverse la direction du binding
Type Exemple
Événement

viewModel

<my-comp (vm-event)="scopeMethod()"/>

écoute les vmEvent dans le viewModel du composant

event DOM
<element ($click)="scopeMethod()"/>

Ecoute le clique sur l’élément

1 way scope to viewModel
<my-comp {vm-prop}="scopeProp"/>

Mettre à jour la propriété vmProp du viewModel avec la valeur de scopeProp

<my-comp {vm-prop}="callExpression()"/>

Mettre à jour la propriété vmProp du viewModel avec la valeur du callExpression

1 way viewModel to scope
Updates scopeProp with the viewModel’s vmProp property.
Metre à jour scopeProp avec la propriété vmProp du viewModel
1 way

scope to attribute

<element {$el-prop}="scopeProp"/>

Mettre à jour l’attribut où la proprité el-prop de l’élément avec la valeur du scopeProp.

C’est l ‘équivalent de el-prop= « {{scopeProp}}

1 way

attribute to scope

<input {^$value}="name"/>

Mettre à jour le name dans le scope avec la propriété value de l’élément

2 way scope to viewModel
<my-comp {(vm-prop)}="scopeProp"/>

Etablir un two-way binding entre vmProp du viewModel et la valeur du scopeProp

2 way scope to attribute
<element {($el-prop)}="scopeProp"/>

Etablir un two-way binding entre l’attribut où la propriété el-prop de l’élement et la valeur scopeProp

2 way attribute to viewModel
<my-comp vm-prop="value"/>

Sets the viewModel’s vmProp to the value of the attribute.
Assigne la valeur de l’attribut à la propriété vmProp du viewModel

Flexibilité du control

Jetons un coup d’oeil à ce que ce que ces nouvelles bindings peuvent faire :

Exemple 1 : binding des événements DOM et two-way bindings

l’exemple suivant crée un élément qui a un comportement similaire d’un élément input natif.

Il utilise un binding d’un événement DOM pour metre à jour sa propriété value quand l’utilisateur clique:

($keydown)='updateValue(%event)'

l’exemple fais une binding transversale de la valeur de <my-input> avec la valeur de l’élément input natif vers la propriété name de person.

<my-input {(value)}="name"/>
<input {($value)}="name"/>

remarquez quand une valeur d’un élément change, la valeur de l’autre élément changera aussi:
JS Bin on jsbin.com

Exemple 2 : Bindings des événements de viewModel

One way bindings et les handler des événements du viewModel peuvent être utilisés pour faire des flux de données vers un sens unique entre les composants.
L’exemple suivant met à jour le nom seulement quand il y a une nombre pair des caractères. Il fais ça par définir une méthode updateNameOnEven qui prend un nouvel nom et met à jour seulement le viewModel de <person-edit> quand le nom a un nombre pair de caractères

  updateNameOnEven: function(newName) { 
    if(newName.length % 2 === 0) { 
      this.attr("name", newName); 
    } 
  }

Après, il écoute quand la valeur de change et appel updateNameOnEven, le passer dans la value de :

(value)="updateNameOnEven( %viewModel.value )"

Finalement il écoute l’évenement oninput et invoque updateNameOnEven et il lui passe la value de <input/>

($input)="updateNameOnEven( %element.value )"

Regarder comment le nom change seulement dans chaque caractère:
JS Bin on jsbin.com

Exemple 3 : Événements ViewModel personnalisés

Les événements du ViewModel que vous pouvez écouter sont pas limités aux named événements déclenchés quand une propriété change. Dans l’exemple suivant le module déclenche des événements quand le bouton save est cliqué en utilisant can.trigger :

can.trigger(self,"saved");

écoute ces événements et invoque sa méthode addMessage avec:

(saved)="addMessage('Saved')"

Vous pouvez remarquer quand save est cliqué, le message  » Saved  » sera affiché temporairement

les Opérateurs clefs

stache ajoute les trois (3) nouveaux operateurs :

  • @clef : l’opérateur  » at « , retourne n’importe quelle valeur at clef (key). Passe la fonction ou compute à clef au lieux de retourné une valeur
  • ~clef: l’opérateur compute, passe un compute au lieu de la valeur dans clef
  • *clef: opérateur d’une variable de template, référence une variable locale du template

Ces clefs peuvent être utilisées par tout où les clefs sont utilisé :

  • Expressions du helper : les arguments passés au helpers stache comme {{myHelper clef1 clef2}}
  • Appel des expressions: les arguments passés aux Appels des experssions comme {{myHelper(clef1,clef2)}}
  • Data bindings: la nouvelle syntaxe du binding comme {vm-prop}="key".
  • Bindings des événements: les arguments passé vers un (event) binding comme ($click)="methode(clef)"

l’opérateur at (@clef)

l’opérateur At utilisé pour passer une fonction où un compute comme un argument au lieu que la fonction ou le compute retourne une valeur .

Cela peut être une bonne manière pour séparer les responsabilités entre les composant.

L’exemple suivant passe la méthode save de l’élément <person-page> à l’élement <person-edit> avec :

{onsave}="@save"

cela permet à d’être capable d’invoquer onsave sans avoir à définir le comportement de save lui même.

Voue pouvez remarquer l’élément désactive les éléments du formulaire quand save est cliqué.

JS Bin on jsbin.com

Quand l’opérateur At est utilisé dans les expressions helper, un compute peut être encore passé. Regardez comment method est un compute qui retourne la fonction func :

JS Bin on jsbin.com

le symbole @ peut être utilisé plusieurs fois dans une référence de clef. Par exemple, si models été une collection de can.Model comme :

  var models = { 
    Task: can.Model.extend({resource: "/tasks"},{}), 
      ... 
  }

pour passer la methode Task.findAll, vous pouriez faire :

<my-grid {get-list}="models@Task@findAll"/>

le premier symbole @ assure que la fonction Task n’est pas invoquée et le deuxieme @ assure que findAll n’est pas invoquée.

L’opérateur compute (~clef)

Dans toutes les utilisations de clef sauf les expressions helper (appel aux expressions, data en bindings des événements), les valeurs sont passées au lieu de compute. D’une maniére générale, les valeurs c’est ce qu’on a besoin. Cependant, des fois c’est bien d’avoir un compute qui vous permet de lire, modifier, et écouter les changements dans cette valeur. L’opérateur compute vous permet de passer un compute de cette valeur au lieu de la valeur elle même.

L’endroit le plus important d’être conscient de cette fonctionnalité appel le helper {{#each}} avec un appel une expression. Le {{#each}} en tant que expression helper comme :

{{#each statsForTeam( teamId.id ) }}

… le résultat de statsForTeam sera passé comme un compute. Cependant, si vous invoquez {{#each}} comme un appel d’une expression comme :

{{#each(statsForTeam( teamId.id ) ) }}

… le résultat de statsForTeam sera passé comme liste. La solution pour ça c’est l’utilisation de l’opérateur compute comme suivant :

{{#each( ~statsForTeam( teamId.id ) ) }}

dans l’exemple suivant each est dans le scope, alors l’aliase eachOf est utilisé à la place :
JS Bin on jsbin.com

opérateur du variable de template

Dans 2.3, vous pouvez créer des variable locales à une template. Les templates ont un context spécial qui peut être utilisé pour stocker les valeurs observables. Ces valeurs observables sont utiles pour connecter les valeur à travers les composants sans besoin de créer les valeurs dans le composant parent.

Les variables de template sont indiquées avec *. L’exemple suivant utilise la variable *editing pour connecter le composant <driver-select/> et le composant <edit-plate>. <driver-select/> exporte le driver selectionné vers *editing avec :

<driver-select {^selected}="*editing"/>

<edit-plate> fais un two way-binding vers 

*editing.licensePlate

 avec:

<edit-plate {(plate-name)}="*editing.licensePlate"/>

Cliquer un driver et editer son numéro plate :

JS Bin on jsbin.com

Amélioration de la Performance

Finalement, la dernière amélioration qu’on a fais c’est l’amélioration du performance de compute. En version 2.3.10, la démo  the spinning circle presque 20 % plus rapide par rapport a 2.2.9 :

2.3

JS Bin on jsbin.com

2.2

JS Bin on jsbin.com

NB: Cet article est une traduction d’un article publié sur le blog de la société Bitovi, avec l’autorisation de monsieur Justin Meyer CEO de Bitovi

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *