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.
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 lethis
pour devenirthis.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.
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.
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.
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.create
où create
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 classeElrondLogic
. 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 destate
dans l’Elfe pour y passer les valeurs initiales (ça ne fonctionnera pas).
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. Fairethis.logic.create()
et donc (au runtime) équivalent à fairethis.logic.create(id)
. Pour des questions de lisibilités, il est fortement recommandé de toujours fournir tous les paramètres à la logique.
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