Ce message est une introduction à XState car il pourrait être utilisé dans un projet Svelte. XState est unique dans l'écosystème JavaScript. Il ne gardera pas votre DOM synchronisé avec votre état d'application, mais il aidera à gérer l'état de votre application en vous permettant de le modéliser en tant que machine à états finis (FSM).
Une plongée profonde dans les machines d'État et les langues formelles dépasse le cadre de ce post, mais Jon Bellah le fait dans un autre article de CSS-Tricks. Pour l'instant, pensez à un FSM comme un graphique de flux. Les graphiques de flux ont un certain nombre d'états, représentés comme des bulles, et des flèches menant d'un état à l'autre, signifiant une transition d'un état à l'autre. Les machines d'État peuvent avoir plus d'une flèche menant à partir d'un État, ou pas du tout si c'est un état final, et ils peuvent même avoir des flèches quittant un État et pointant tout de suite dans ce même état.
Si tout cela semble écrasant, nous détendrons, nous entrerons dans tous les détails, agréable et lent. Pour l'instant, la vue de haut niveau est que, lorsque nous modélisons notre application en tant que machine d'état, nous créerons différents «états» que notre application peut être dans (Get It… Machine d'état… états?), Et les événements qui se produisent et provoqueront des modifications de l'état seront les flèches entre ces états. XState appelle les États «États» et les flèches entre les États «actions».
XState a une courbe d'apprentissage, ce qui rend difficile à enseigner. Avec un cas d'utilisation trop artificiel, il semblera inutilement complexe. Ce n'est que lorsque le code d'une application est un peu emmêlé que Xstate brille. Cela rend l'écriture à ce sujet délicat. Cela dit, l'exemple que nous examinerons est un widget d'observance automatique (parfois appelé Autosuggest), ou une boîte d'entrée qui, une fois cliqué, révèle une liste d'éléments à choisir, quel filtrez-vous lorsque vous tapez dans l'entrée.
Pour ce post, nous envisagerons de nettoyer le code d'animation. Voici le point de départ:
Il s'agit du code réel de ma bibliothèque Svelte-Helpers, bien qu'avec des pièces inutiles supprimées pour ce post. Vous pouvez cliquer sur l'entrée et filtrer les éléments, mais vous ne pourrez rien sélectionner, «Arrow Down» via les éléments, survolez, etc. J'ai supprimé tout le code qui n'est pas pertinent pour cet article.
Nous examinerons l'animation de la liste des éléments. Lorsque vous cliquez sur l'entrée et que la liste des résultats rend d'abord, nous voulons l'animer. Lorsque vous tapez et filtrez, les modifications des dimensions de la liste animeront les plus grandes et les plus petites. Et lorsque l'entrée perd le focus, ou que vous cliquez sur ESC, nous animons la hauteur de la liste à zéro, tout en le décollant, puis en le supprimons du DOM (et pas avant). Pour rendre les choses plus intéressantes (et gentilles pour l'utilisateur), utilisons une configuration de ressort différente pour l'ouverture que ce que nous utilisons pour la fermeture, de sorte que la liste se ferme un peu plus rapidement ou raidement, donc UX inutile ne s'attarde pas trop longtemps à l'écran.
Si vous vous demandez pourquoi je n'utilise pas les transitions Svelte pour gérer les animations dans et hors du DOM, c'est parce que j'animant également les dimensions de la liste lorsqu'elle est ouverte, car l'utilisateur filtre, et la coordination entre la transition, et les animations de printemps régulières est beaucoup plus difficile que d'attendre simplement une mise à jour de ressort pour terminer zéro avant de retirer un élément de l'élément. Par exemple, que se passe-t-il si l'utilisateur tape et filtre rapidement la liste, car il s'animance? Comme nous le verrons, Xstate fait des transitions d'état délicat comme ceci facilement.
Jetons un coup d'œil au code de l'exemple jusqu'à présent. Nous avons une variable ouverte à contrôler lorsque la liste est ouverte et une propriété ResulteListVisible pour contrôler si elle doit être dans le DOM. Nous avons également une variable de clôture qui contrôle si la liste est en cours de fermeture.
Sur la ligne 28, il existe une méthode INPUTENGAGED qui s'exécute lorsque l'entrée est cliquée ou concentrée. Pour l'instant, notez simplement qu'il s'ouvre et ResultsListVisible sur true. InputChanged est appelé lorsque l'utilisateur type dans l'entrée et set ouverte sur true. C'est pour que l'entrée soit concentrée, l'utilisateur clique sur Escape pour le fermer, mais commence ensuite à taper, afin qu'il puisse rouvrir. Et, bien sûr, la fonction InputBlurred s'exécute lorsque vous vous attendez, et définit la fermeture de True et s'ouvre à False.
Courons à part ce gâchis enchevêtré et voyons comment les animations fonctionnent. Remarquez le Slideinspring et l'OpacitySpring en haut. L'ancien glisse la liste de haut en bas et ajuste la taille à mesure que l'utilisateur type. Ce dernier s'estompe la liste lorsqu'il est caché. Nous nous concentrerons principalement sur le SlideInspring.
Jetez un œil à la monstruosité d'une fonction appelée setspringdimensions. Cela met à jour notre printemps de diapositive. En nous concentrant sur les pièces importantes, nous prenons quelques propriétés booléennes. Si la liste est ouverte, nous définissons la configuration de ressort d'ouverture, nous définissons immédiatement la largeur de la liste (je veux que la liste ne glisse que vers le bas, pas vers le bas et à l'extérieur), via la configuration {dur: true}, puis définissez la hauteur. Si nous clôturons, nous animons à zéro et, lorsque l'animation est terminée, nous définissons les résultats de Résultats à false (si l'animation de clôture est interrompue, Svelte sera assez intelligent pour ne pas résoudre la promesse afin que le rappel ne s'exécutera jamais). Enfin, cette méthode est également appelée chaque fois que la taille de la liste des résultats change, c'est-à-dire que l'utilisateur filtre. Nous avons mis en place un ResizeObserver ailleurs pour gérer cela.
Faisons le point sur ce code.
Avez-vous attrapé le bug? Lorsque le bouton ESC est enfoncé, je ne mets que ouvert sur False. J'ai oublié de se fermer sur True et d'appeler SetSpringDimensions (False, True). Ce bug n'a pas été délibérément artificiel pour ce billet de blog! C'est une véritable erreur que j'ai faite lorsque je révisais les animations de ce widget. Je pouvais simplement copier coller le code en entrée Blurée jusqu'à l'endroit où le bouton d'échappement est capturé, ou même le déplacer vers une nouvelle fonction et l'appeler depuis les deux endroits. Ce bogue n'est pas fondamentalement difficile à résoudre, mais il augmente la charge cognitive du code.
Il y a beaucoup de choses dont nous gardons une trace, mais pire encore, cet état est dispersé dans tout le module. Prenez n'importe quel élément d'état décrit ci-dessus et utilisez la fonctionnalité de recherche de codes et de Box pour afficher tous les endroits où ce morceau d'état est utilisé. Vous verrez votre curseur rebondir à travers le fichier. Imaginez maintenant que vous êtes nouveau dans ce code, essayant de le comprendre. Pensez au modèle mental croissant de toutes ces pièces d'État dont vous devrez garder une trace, déterminer comment cela fonctionne en fonction de tous les endroits où il existe. Nous avons tous été là; Ça craint. XState offre une meilleure façon; Voyons comment.
Retenons un peu en arrière. Ne serait-il pas plus simple de modéliser notre widget en termes de l'état dans lequel il se trouve, avec des événements qui se produisent lorsque l'utilisateur interagit, qui provoquent des effets secondaires et des transitions vers de nouveaux États? Bien sûr, mais c'est ce que nous faisions déjà; Le problème est que le code est dispersé partout. XState nous donne la possibilité de modéliser correctement notre état de cette manière.
Ne vous attendez pas à ce que XState fasse magiquement toute notre complexité disparaître. Nous devons toujours coordonner nos ressorts, ajuster la configuration du ressort en fonction de l'ouverture et de la fermeture des états, de la gestion des redimensions, etc. Ce que XState nous donne, c'est la possibilité de centraliser ce code de gestion de l'État d'une manière facile à raisonner et à nous ajuster. En fait, notre nombre global de lignes augmentera un peu, en raison de la configuration de notre machine d'État. Jetons un coup d'œil.
Sautons et voyons à quoi ressemble une machine d'État nue. J'utilise le package FSM de XState, qui est une version minimale et réprimée de XState, avec une petite taille de bundle de 1kb, parfaite pour les bibliothèques (comme un widget Autosuggest). Il n'a pas beaucoup de fonctionnalités avancées comme le package XState complet, mais nous n'aurions pas besoin d'eux pour notre cas d'utilisation, et nous ne voudrions pas qu'ils ne les souhaitent qu'un article d'introduction comme celui-ci.
Le code de notre machine d'état est ci-dessous, et la démo interactive est terminée sur Code Sandbox. Il y en a beaucoup, mais nous allons passer en revue sous peu. Et pour être clair, cela ne fonctionne pas encore.
const statemachine = CreateMachine ( { Initial: "Initial", contexte: { ouvert: faux, nœud: null }, déclare: { initial: { sur: {Open: "Open"} }, ouvrir: { sur: { Rendu: {Actions: "Rendu"}, Redimensi: {actions: "redimensi"}, Close: "Clôture" }, Entrée: "ouverte" }, Clôture: { sur: { Open: {Target: "Open", Actions: ["Resize"]}, Fermé: "fermé" }, Entrée: "Fermer" }, fermé: { sur: { Ouvert: "Ouver" }, Entrée: "fermé" } } }, { Actions: { ouvert: attribution (context => { return {... context, ouvert: true}; }), Rendu: attribution ((contexte, evt) => { const {node} = evt; return {... context, node}; }), fermer() {}, redimensit (context) {}, fermé: attribution (() => { return {open: false, noeud: null}; }) } } ));
Passons de haut en bas. La propriété initiale contrôle quel est l'état initial, ce que j'ai appelé «initial». Le contexte est les données associées à notre machine d'état. Je stockage un booléen pour savoir si la liste des résultats est actuellement ouverte, ainsi qu'un objet de nœud pour cette même liste de résultats. Ensuite, nous voyons nos États. Chaque État est une clé de la propriété des États. Pour la plupart des États, vous pouvez voir que nous avons une propriété sur une propriété et une propriété d'entrée.
sur configure les événements. Pour chaque événement, nous pouvons passer à un nouvel état; Nous pouvons exécuter des effets secondaires, appelés actions; ou les deux. Par exemple, lorsque l'événement ouvert se produit à l'intérieur de l'état initial, nous passons à l'état ouvert. Lorsque l'événement rendu se produit à l'état ouvert, nous exécutons l'action rendue. Et lorsque l'événement ouvert se produit à l'intérieur de l'état de clôture, nous passons à l'état ouvert et faisons également l'action de redimensionnement. Le champ d'entrée que vous voyez sur la plupart des états configure une action à exécuter automatiquement chaque fois qu'un état est entré. Il y a aussi des actions de sortie, même si nous n'en avons pas besoin ici.
Nous avons encore quelques choses supplémentaires à couvrir. Voyons comment les données ou le contexte de notre machine d'État peuvent changer. Lorsque nous voulons une action pour modifier le contexte, nous l'enroulons en attribution et renvoyons le nouveau contexte de notre action; Si nous n'avons pas besoin de traitement, nous pouvons simplement passer le nouvel état directement à attribuer. Si notre action ne met pas à jour le contexte, c'est-à-dire que c'est juste pour les effets secondaires, alors nous n'enveloppez pas notre fonction d'action en attribution et effectuons simplement les effets secondaires dont nous avons besoin.
Nous avons un modèle sympa pour notre machine d'État, mais comment l' exécuter ? Nous utilisons la fonction d'interprétation.
const stateMachineService = interpréter (stateMachine) .start ();
Maintenant, StateMachineService est notre machine d'État en cours d'exécution, sur laquelle nous pouvons invoquer des événements pour forcer nos transitions et nos actions. Pour licencier un événement, nous appelons envoyer, passer le nom de l'événement, puis, éventuellement, l'objet de l'événement. Par exemple, dans notre action svelte qui s'exécute lorsque la liste des résultats monte pour la première fois dans le DOM, nous avons ceci:
StateMachineService.Send ({type: "Rendu", Node});
C'est ainsi que l'action rendue obtient le nœud de la liste des résultats. Si vous regardez autour du reste du fichier Auto-Aomplete.Svelte, vous verrez tout le code de gestion de l'état ad hoc remplacé par des répartitions d'événements à ligne unique. Dans le gestionnaire d'événements pour notre clic / concentration d'entrée, nous exécutons l'événement ouvert. Notre ResizeObserver déclenche l'événement de redimensionnement. Et ainsi de suite.
Arrêtons un instant et apprécions les choses que XState nous donne gratuitement ici. Regardons le gestionnaire qui s'exécute lorsque notre entrée est cliquée ou concentrée avant d'ajouter XState.
Fonction InputEngaged (EVT) { if (fermeture) { setspringDimensions (); } Open = true; ResultsListVisible = true; }
Avant, nous vérifiions si nous fermions si nous fermions, et dans le cas, forçant une recul de notre printemps coulissant. Sinon, nous avons ouvert notre widget. Mais que s'est-il passé si nous cliquions sur l'entrée alors qu'elle était déjà ouverte? Le même code re-ran. Heureusement, cela n'avait pas vraiment d'importance. Svelte ne se soucie pas si nous réinitialisons Open et ResultsListVisible aux valeurs qu'ils détenaient déjà. Mais ces préoccupations disparaissent avec XState. La nouvelle version ressemble à ceci:
Fonction InputEngaged (EVT) { StateMachineService.send ("Open"); }
Si notre machine d'État est déjà à l'état ouvert et que nous licencions l'événement ouvert, alors rien ne se passe, car il n'y a pas d'événement ouvert configuré pour cet état. Et cette manipulation spéciale pour le moment où l'entrée est cliquée lorsque les résultats se ferment? Cela est également géré directement dans la configuration de la machine d'état - remarquez comment l'événement ouvert s'accumule sur l'action de redimensionnement lorsqu'il est exécuté à partir de l'état de clôture.
Et, bien sûr, nous avons corrigé le bug de la clé ESC de avant. Maintenant, appuyer sur la touche déclenche simplement l'événement proche, et c'est tout.
La fin est presque anti-climatique. Nous devons prendre tout le travail que nous faisions auparavant et le déplacer simplement au bon endroit parmi nos actions. XState ne supprime pas la nécessité pour nous d'écrire du code; Il ne fournit qu'un endroit structuré et clair pour le dire.
{ Actions: { ouvert: attribution ({open: true}), Rendu: attribution ((contexte, evt) => { const {node} = evt; const dimensions = getResultsListDimensions (nœud); ItemSheightObserver.Observe (nœud); opacityspring.set (1, {hard: true}); Object.Assign (SlideinsPring, Slide_Open); Slideinspring.update (prev => ({... prev, largeur: dimensions.width}), { dur: vrai }); SlideinsPring.set (dimensions, {hard: false}); return {... context, node}; }), fermer() { opacityspring.set (0); Object.Assign (SlideinsPring, Slide_close); Slideinspring .Update (prev => ({... prev, hauteur: 0})) .Then (() => { StateMachineService.send ("fermé"); }); }, redimensionner (contexte) { opacityspring.set (1); SlideInspring.set (getResultsListDimensions (context.Node)); }, fermé: attribution (() => { itemSheightObserver.UnObserve (ResulteList); return {open: false, noeud: null}; }) } }
Notre état d'animation est dans notre machine d'État, mais comment le sortir ? Nous avons besoin de l'état ouvert pour contrôler le rendu de notre liste de résultats et, bien que non utilisé dans cette démo, la version réelle de ce widget Autosuggest a besoin du nœud DOM de la liste des résultats pour des choses comme le défilement de l'élément actuellement mis en surbrillance.
Il s'avère que notre stateCachineService a une méthode d'abonnement qui tire chaque fois qu'il y a un changement d'état. Le rappel que vous passez est invoqué avec l'état de la machine d'état actuel, qui comprend un objet de contexte. Mais Svelte a une astuce spéciale dans sa manche: sa syntaxe réactive de $: ne fonctionne pas seulement avec les variables de composants et les magasins Svelte; Il fonctionne également avec n'importe quel objet avec une méthode d'abonnement. Cela signifie que nous pouvons nous synchroniser avec notre machine d'état avec quelque chose d'aussi simple que ceci:
$: ({Open, Node: ResultsList} = $ StateMachineService.Context);
Juste une destruction régulière, avec quelques parens pour aider les choses à analyser correctement.
Une note rapide ici, comme domaine d'amélioration. À l'heure actuelle, nous avons certaines actions qui effectuent toutes deux un effet secondaire et mettent également à jour l'état. Idéalement, nous devrions probablement les diviser en deux actions, l'une juste pour l'effet secondaire, et l'autre en utilisant Assign pour le nouvel état. Mais j'ai décidé de garder les choses aussi simples que possible pour cet article pour aider à atténuer l'introduction de XState, même si quelques choses ne sont pas tout à fait idéales.
J'espère que ce message a suscité un certain intérêt pour XState. J'ai trouvé que c'était un outil incroyablement utile et facile à utiliser pour gérer un état complexe. Sachez que nous n'avons fait que rayer la surface. Nous nous sommes concentrés sur le package FSM minimal, mais l'ensemble de la bibliothèque XState est capable de bien plus que ce que nous avons couvert ici, des états imbriqués, au support de première classe pour les promesses, et il a même un outil de visualisation d'état! Je vous exhorte à le vérifier.
Codage heureux!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!