Son état

Est-ce que votre Elfe est malade ? Il est dans quel état ? Ici on ne traite pas de ses caractéristiques, mais bien de son état général.

Donner un état à votre Elfe

Maintenant que vous avez vu à quoi ressemblent les propriétés, qui sont l’équivalent des setX Goblins, voyons comment faire pour donner un état (state) à un Elfe. Pour rappel, le state doit contenir uniquement des valeurs sérialisables (il peut y avoir des exceptions non documentées ici). Les différences entre les propriétés et les states ne se limitent pas au protected / public mais à ce qu’on désire faire avec le service. Le state permet de gérer les mutations dans une fonction pure (sans effet de bord) que l’on nomme reducer. Il est possible ainsi de réaliser des tests efficacement sur les states sans faire intervenir les effets de bords qui doivent être produits uniquement par les quêtes. De plus, les states sont immuables (immutables) par nature, ce qui donne de nombreux avantages pour le fonctionnement du framework. Néanmoins, concernant les Elfes, les states sont mutables (du point de vue du consomateur) à la différence des Goblins où tout doit être explicite.

Les states doivent également être utilisés pour la persistance des données contrairement aux propriétés. Nous allons y revenir plus loin.

Bien qu’on parle toujours de reducer, les Elfes n’utilisent pas le même prototype qu’un reducer habituel. Voici celui que vous connaissez bien et qui est similaire au reduce d’un Array :

(state, action) => state.set();

Avec les Elfes, l’action est décomposée en arguments et le state est injecté dans le this pour devenir this.state :

(val1, val2, ..., valN) => this.state.valN = valN;

Dans l’exemple ci-dessus vous pouvez voir this.state.valN = valN. Connaissant bien les Goblins, vous devriez être surpris. Il n’y a plus besoin d’effectuer un .set() pour assigner une valeur. De même avec la lecture, le .get() n’existe plus.

Bien entendu, en dehors du reducer, le state est toujours immuable.

Les Elfes se doivent d’être beau

Un Elfe est un être typé. Il connait son état dans ses moindres détails.

const {id} = require('xcraft-core-goblin/lib/types.js');
const {string, number} = require('xcraft-core-stones');

class ElrondShape {
  id = id('elrond');
  name = string;
  yearsOfLife = number;
}

class ElrondState extends Elf.Sculpt(ElrondShape) {}

L’exemple ci-dessus est un shape. Ce shape permet de décrire précisément à quoi doit ressembler un state. Une fonction elfique particulière, Elf.Sculpt(), permet de sculpter un state elfique à partir d’un shape.

Pour bien comprendre les termes, voyez la classe shape comme une définition, et la classe state comme un type. C’est ce type qui va permettre d’instancier un state à notre Elfe.

Consultez directement la documentation du module xcraft-core-stones si vous souhaitez vous former sur les types.

Les Elfes ont de la logique

Quand un Elfe réfléchit, il se fie à sa mémoire structurée et bien définie. Cette mémoire est une instance du type décrit précédemment.

class ElrondLogic extends Elf.Spirit {
  /* Instance du state Elrond avec des valeurs initiales */
  state = new ElrondState({
    id: undefined,
    name: 'Elrond',
    yearsOfLife: 0,
  });

  /* Reducer pour la quête create */
  create(id) {
    const {state} = this;
    state.id = id;
  }

  /* Reducer pour la quête nextYear */
  nextYear() {
    const {state} = this;
    state.yearsOfLife++;
  }
}

La logique de l’Efle doit dériver de l’esprit Elfe Elf.Spirit. En effet, tous les Elfes partagent les mêmes fondamentaux.

Il existe un second type de logic que nous allons découvrir dans la section Gravé dans la pierre.

Nous sommes ici dans la définitions des reducers et de l’état initial. La propriété state est réservée à ce but et doit toujours se nommer ainsi. Le this des reducers est un peu particulier. Comprennez bien que le this ne représente pas le this de la classe Elfe. Les reducers n’ont pas vraiment changés par rapport aux Goblins. Vous êtes ici dans le résultat d’un dispatch Redux où tout est pure. C’est depuis ce this que vous pouvez récupérer (si vous le souhaitez) le state immuable via this.immutable.

Pour rappel, un reducer (ici) est toujours synchrone et pure. Même si techniquement il y a des moyens de contourner les mécaniques, vous devez effectuer les appels asynchrones uniquement depuis les quêtes, puis transmettre les données aux reducers. Ne tentez jamais de réaliser des effets de bords (même synchrones, comme par exemple avec fs.readFileSync) depuis les reducers; c’est un anti-pattern; Bouhhhh.

Les Elfes partent en quêtes

La classe de l’Elfe …

class Elrond extends Elf {
  logic = Elf.getLogic(ElrondLogic); /* Accès aux reducers */
  state = new ElrondState();

  /* La quête (constructeur) */
  async create(id, desktopId = null) {
    this.logic.create(id);
    return this;
  }

  /* La quête (effets de bord) */
  async nextYear() {
    this.logic.nextYear();
    return this.state.yearsOfLife;
  }
}

Mais ou est le quest.do bien connu des Goblin ?

Oubliez le, bien que techniquement il existe toujours, vous ne devez jamais l’utiliser avec les Elfes. Ici les reducers sont directement appelables avec leur nom. Pour ce faire il faut avoir accès à la logique sous la forme d’une propriété logic. Celle-ci va vous donner (avec complétion) l’accès à tous les reducers de cet Elfe. Vous pouvez alors aussi bien appeler le propre reducer de la quête (this.logic.createcreate correspond à la quête en cours), que le reducer d’une autre quête, ce qui techniquement correspond à un quest.dispatch dans le monde Goblin.

L’Elfe décrit ses quêtes dans sa classe, en plus des propriétés. Les reducers sont séparés volontairement et peuvent alors être testés indépendamment de l’Elfe. Pour atteindre l’état de l’Elfe, il suffit d’utiliser la propriété this.state. Notez bien que pour y avoir accès, vous devez la déclarer comme propriété de l’Elfe.

Peut-être que vous trouvez cela étrange car vous avez aussi déclaré une propriété state dans la classe ElrondLogic. Je vous propose qu’on garde un peu de mystère. Faites comme expliqué dans cette documentation et tout ira bien. N’utilisez pas l’instance de state dans l’Elfe pour y passer les valeurs initiales (ça ne fonctionnera pas).

Comment injecter des propriétés supplémentaires dans le reducer ?

Comme vous le savez bien avec les Goblins, tous les paramètres donnés à une quête sont forcément disponibles dans l’action passé au reducer. Pour ce qui est des Elfes, c’est exactement la même chose, et comme avec les Goblins, il est possible d’en donner d’autres.

L’exemple ci-dessous permet de montrer que l’on peut récupérer directement l’id depuis le reducer, mais également l’age qui ne fait pas partie du prototype de la quête create. Ce n’est pas un problème car l’age est donné explicitement avec l’appel sur la “logic” this.logic.create().

class ElrondLogic extends Elf.Spirit {
  /* ... */

  create(id, age) {
    const {state} = this;
    state.id = id;
    state.age = age;
  }
}

class Elrond extends Elf {
  /* ... */

  async create(id, desktopId = null) {
    this.logic.create(id, 143);
    return this;
  }
}

Si on avait aussi spécifié desktopId dans le prototype de la méthode create de la logique, on aurait également pu le récupérer directement sans le spécifier avec l’appel this.logic.create().

Pour rappel, derrière tout ceci il y a toujours un Goblin. Ce qui veut dire que les paramètres passés à la quête (create dans cet exemple) sont forcément tous, toujours récupérables depuis la logique. Faire this.logic.create() et donc (au runtime) équivalent à faire this.logic.create(id). Pour des questions de lisibilités, il est fortement recommandé de toujours fournir tous les paramètres à la logique.

Exporter la logique, sinon votre Elfe va rester stupide

C’est à la naissance de l’Elfe que sa logique est construite. Les bébé Elfes ne sont pas comme les autres.

const {Elf} = require('xcraft-core-goblin');
const {Elrond, ElrondLogic} = require('./lib/elrond.js');

exports.xcraftCommands = Elf.birth(Elrond, ElrondLogic);

Voir aussi : La naissance