Bonjour, Habr! J'attire votre attention sur une traduction de l'article "React Fibre Architecture" d' Andrew Clark .
Entrée
React Fibre est une implémentation progressive de l'algorithme clé React. Il s'agit de l'aboutissement d'une étude de deux ans menée par l'équipe de développement de React.
L'objectif de Fibre est d'augmenter la productivité lors du développement de tâches telles que l'animation, l'organisation d'éléments sur une page et le déplacement d'éléments. Sa principale caractéristique est le rendu incrémentiel: la possibilité de diviser le travail de rendu en unités et de les répartir entre plusieurs images.
Les autres fonctionnalités clés incluent la possibilité de suspendre, d'annuler ou de réutiliser les mises à jour entrantes de l'arborescence DOM, la possibilité de hiérarchiser différents types de mises à jour, ainsi que la coordination des primitives.
Avant de lire cet article, nous vous recommandons de vous familiariser avec les principes de base de React:
Revue
Qu'est-ce que la réconciliation?
La réconciliation est un algorithme React utilisé pour distinguer un arbre d'éléments d'un autre pour déterminer les pièces qui devront être remplacées.
Une mise à jour est un changement dans les données utilisées pour rendre une application React. Ceci est généralement le résultat de l'appel de la méthode setState; Le résultat final du rendu du composant.
L'idée clé de l'API React est de penser aux mises à jour comme si elles pouvaient conduire à un rendu complet de l'application. Cela permet au développeur d'agir de manière déclarative, sans se soucier de la rationalité de la transition de l'application d'un état à un autre (de A à B, B à C, C à A, etc.).
En général, le rendu de l'application entière pour chaque modification ne fonctionne que dans les applications les plus traditionnelles. Dans le monde réel, cela affecte négativement les performances. L'acte comprend des optimisations qui créent une vue de rendu complète sans affecter une énorme part des performances. La plupart de ces optimisations impliquent un processus appelé réconciliation.
La réconciliation est un algorithme derrière ce que nous avons l'habitude d'appeler «DOM virtuel». La définition ressemble à ceci: lorsque vous effectuez le rendu d'une application React, l'arborescence des éléments qui décrit l'application est générée dans la mémoire réservée. Cet arbre est ensuite inclus dans l'environnement de rendu - en utilisant l'exemple d'une application de navigateur, il est traduit en un ensemble d'opérations DOM. Lorsque l'état de l'application est mis à jour (généralement en appelant setState), une nouvelle arborescence est générée. La nouvelle arborescence est comparée à la précédente pour calculer et activer exactement les opérations nécessaires pour redessiner l'application mise à jour.
Bien que Fibre soit une implémentation proche du réconciliateur, l'algorithme de haut niveau expliqué dans la documentation React sera à peu près le même.
Concepts clés:
- Différents types de composants suggèrent la génération d'arbres sensiblement différents. React n'essaiera pas de les comparer, mais remplacera simplement l'ancien arbre complètement.
- Les listes sont distinguées à l'aide de clés. Les clés doivent être «persistantes, prévisibles et uniques».
Réconciliation vs rendu
L'arbre DOM est l'un des environnements que React peut dessiner, le reste peut être attribué à des vues natives iOS et Android à l'aide de React Native (c'est pourquoi Virtual Dom est un petit nom inapproprié).
La raison pour laquelle React prend en charge tant d'objectifs est que React est conçu de manière à ce que la réconciliation et le rendu soient des phases distinctes. Le réconciliateur, qui fonctionne, calcule quelles parties de l'arborescence ont changé, le moteur de rendu utilise ensuite ces informations pour mettre à jour l'arborescence précédemment rendue.
Cette séparation signifie que React DOM et React Native peuvent utiliser leurs propres mécanismes de rendu lorsqu'ils utilisent le même outil de mise en cache, qui se trouve dans React Core.
La fibre est une implémentation repensée de l'algorithme de réconciliation. Il a une relation indirecte avec le rendu, tandis que les mécanismes de rendu (rendus) peuvent être modifiés pour prendre en charge tous les avantages de la nouvelle architecture.
La planification est un processus qui détermine quand les travaux doivent être terminés.
Travail - tous les calculs qui doivent être effectués. Le travail est généralement le résultat d'une mise à jour (par exemple, l'appel de setState).
Les principes de l'architecture React sont si bons qu'ils ne peuvent être décrits qu'avec cette citation:
Dans l'implémentation actuelle de React, il parcourt récursivement l'arborescence et appelle les fonctions de rendu sur la totalité de l'arborescence mise à jour en un seul tick (16 ms). Cependant, à l'avenir, il pourra annuler certaines mises à jour pour éviter les sauts de trame.
Il s'agit d'un sujet fréquemment discuté concernant React Design. Certaines bibliothèques populaires implémentent une approche «push», où les calculs sont effectués lorsque de nouvelles données sont disponibles. Cependant, React adhère à l'approche pull, où les calculs peuvent être annulés si nécessaire.
React n'est pas une bibliothèque de traitement de données généralisées. Il s'agit d'une bibliothèque pour créer des interfaces utilisateur. Nous pensons qu'il devrait avoir une position unique dans l'application afin de déterminer quels calculs sont appropriés et lesquels ne le sont pas pour le moment.
Si quelque chose se déroule dans les coulisses, nous pouvons annuler toute la logique qui lui est associée. Si les données arrivent plus rapidement que le taux de rendu d'image, nous pouvons combiner les mises à jour. Nous pouvons augmenter la priorité du travail résultant de l'interaction de l'utilisateur (comme l'apparition d'une animation lorsqu'un bouton est enfoncé) par rapport à un travail moins important en arrière-plan (rendre le nouveau contenu chargé à partir du serveur) pour empêcher les téléchargements de trames.
Concepts clés:
- Dans les interfaces utilisateur, il n'est pas important que chaque mise à jour soit appliquée immédiatement; en fait, ce comportement sera superflu, il contribuera à la chute des frames et à la détérioration de l'UX.
- Différents types de mises à jour ont des priorités différentes - les mises à jour d'animation devraient se terminer plus rapidement que, par exemple, la mise à jour du stockage de données.
- Une approche push nécessite que l'application (vous, le développeur) décide comment planifier le travail. Une approche basée sur l'extraction permet au cadre de prendre des décisions pour vous.
Réagir pour le moment n'a pas l'avantage de planifier dans une large mesure; les résultats de mise à jour de la sous-arborescence entière seront dessinés immédiatement. La sélection judicieuse des éléments de l'algorithme du noyau React pour appliquer la planification est l'idée clé de Fibre.
Qu'est-ce que la fibre?
Nous aborderons le cœur de l'architecture React Fibre. La fibre est une abstraction de niveau inférieur sur l'application à laquelle les développeurs ont l'habitude de penser. Si vous considérez vos tentatives pour le comprendre comme désespérées, ne vous découragez pas (vous n'êtes pas seul). Continuez à chercher et cela portera enfin ses fruits.
Et donc!
Nous avons atteint cet objectif principal de l'architecture Fibre - laisser React profiter de la planification. Plus précisément, nous devons être en mesure de:
- arrêter le travail et y revenir plus tard.
- prioriser différents types de travaux.
- réutiliser le travail effectué précédemment.
- annuler le travail s'il n'est plus nécessaire.
Pour faire tout cela, nous devons d'abord diviser le travail en unités. Dans un sens, c'est de la fibre. La fibre représente une unité de travail.
Pour aller plus loin, revenons au concept de base de React "composants as function data" , souvent exprimé comme:
v = f(d)
Il s'ensuit que le rendu d'une application React revient à appeler une fonction dont le corps contient des appels à d'autres fonctions, etc. Cette analogie est utile lorsque l'on pense aux fibres.
La façon dont les ordinateurs vérifient essentiellement l'ordre d'exécution d'un programme est appelée une pile d'appels. Une fois la fonction terminée, le nouveau conteneur de pile est ajouté à la pile. Ce conteneur de pile représente le travail effectué par une fonction.
Lorsque vous travaillez avec des interfaces utilisateur, trop de travail est effectué immédiatement et c'est un problème, cela peut entraîner des sauts dans l'animation et semblera intermittent. De plus, une partie de ce travail peut ne pas être nécessaire s'il est remplacé par la mise à jour la plus récente. À ce stade, la comparaison entre l'interface utilisateur et la fonction diverge, car les composants ont une responsabilité plus spécifique que les fonctions en général.
Les derniers navigateurs et React Native implémentent des API qui aident à résoudre ce problème:
requestIdleCallback distribue les tâches de façon à ce que les fonctions de faible priorité soient appelées dans une période simple, et requestAnimationFrame distribue les tâches de sorte que les fonctions de haute priorité soient appelées dans la trame suivante. Le problème est que pour utiliser ces API, vous devez diviser le travail de rendu en unités incrémentielles. Si vous ne comptez que sur la pile d'appels, le travail se poursuivra jusqu'à ce que la pile soit vide.
Ne serait-il pas intéressant de pouvoir personnaliser le comportement de la pile d'appels pour optimiser l'affichage de certaines parties de l'interface utilisateur? Serait-ce bien si nous pouvions casser la pile d'appels pour manipuler les conteneurs manuellement?
C'est la vocation de React Fibre. La fibre est une nouvelle implémentation de pile adaptée aux composants React. Vous pouvez considérer une fibre unique comme un conteneur de pile virtuelle.
L'avantage de cette implémentation de la pile est que vous pouvez enregistrer la pile de conteneurs en mémoire et l'exécuter ensuite (et où) vous le souhaitez. Il s'agit d'une définition cruciale pour atteindre vos objectifs de planification.
En plus de la planification, des actions manuelles avec la pile révèlent le potentiel de concepts tels que la cohérence (simultanéité) et la gestion des erreurs (limites d'erreur).
Dans la section suivante, nous examinons la structure des fibres.
Structure en fibre
Plus précisément, une «fibre» est un objet JavaScript qui contient des informations sur un composant, son entrée et sa sortie.
La fibre est cohérente avec le conteneur de pile, mais elle est également cohérente avec l'essence du composant.
Voici quelques propriétés importantes de la «fibre» (Cette liste n'est pas exhaustive):
Type et clé
Le type et la clé servent la fibre ainsi que les éléments React. En fait, lorsqu'une fibre est créée, ces deux champs y sont copiés directement.
Le type de fibre décrit le composant auquel elle correspond. Pour la composition des composants, le type est une fonction ou une classe de composants. Pour les composants de service (div, span), le type est une chaîne.
Conceptuellement, un type est une fonction dont l'exécution est suivie par un conteneur de pile.
Avec le type, la clé est utilisée lors de la comparaison des arbres pour déterminer si la fibre peut être réutilisée.
Enfant et frère
Ces champs pointent vers d'autres fibres, décrivant la structure récursive des fibres.
L'enfant fibre correspond à la valeur qui a été renvoyée suite à l'appel de la méthode de rendu sur le composant. Dans l'exemple ci-dessous:
function Parent() { return <Child /> }
Parent Fiber Child correspond à Child.
Le champ relatif (ou voisin) est utilisé si le rendu renvoie plusieurs enfants (une nouvelle fonctionnalité dans Fibre):
function Parent() { return [<Child1 />, <Child2 />] }
Les fibres enfants sont une liste liée individuellement à la tête de laquelle se trouve le premier enfant. Ainsi, dans cet exemple, l'enfant Parent est Child1 et les parents de Child1 sont Child2.
Pour revenir à notre analogie avec les fonctions, vous pouvez penser à une fibre enfant comme une fonction appelée à la fin (fonction appelée queue).
Exemple Wikipédia:
function foo(data) { a(data); return b(data); }
Dans cet exemple, la fonction appelée queue est b.
Valeur de retour (retour)
La fibre de retour est la fibre vers laquelle le programme doit retourner après avoir traité la fibre actuelle. Cela revient à renvoyer l'adresse du conteneur de pile.
Elle peut également être considérée comme une fibre parente.
Si une fibre a plusieurs fibres enfants, le retour de chaque fibre enfant renvoie la fibre parent. Dans l'exemple ci-dessus, la fibre de retour de Child1 et Child2 est Parent.
Propriétés actuelles et mises en cache (en attenteProps et memoredProps)
Conceptuellement, les propriétés sont des arguments de fonction. Les propriétés de fibre actuelles sont un ensemble de ces propriétés au début de l'exécution, celles mises en cache sont un ensemble à la fin de l'exécution.
Lorsque les propriétés d'attente d'entrée sont mises en cache, cela signifie que la sortie de fibre précédente peut être réutilisée sans aucun calcul.
Priorité des travaux en cours (en attenteWorkPriority)
La quantité de travail déterminant la priorité est affichée par la fibre. Le module de niveau de priorité dans React ReactPrioritylevel comprend différents niveaux de priorité et ce qu'ils représentent.
En commençant par une exception de type NoWork qui est 0, un nombre plus élevé définit la priorité la plus basse. Par exemple, vous pouvez utiliser la fonction suivante pour vérifier si la priorité de fibre est supérieure au niveau spécifié:
function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority }
Cette fonction est uniquement à des fins d'illustration; il ne fait pas partie de la base de données React Fibre.
L'ordonnanceur utilise le champ prioritaire pour trouver l'unité de travail suivante qui peut être effectuée. Nous discuterons de cet algorithme dans la section suivante.
Alternative (ou paire)
Mise à jour (flush) de la fibre - cela signifie afficher sa sortie sur l'écran.
Fibre en développement (travaux en cours) - fibre qui n'a pas encore été construite; en d'autres termes, c'est un conteneur de pile qui n'a pas encore été retourné.
A tout moment, l'essence du composant n'a pas plus de deux états pour la fibre qui correspond à: fibre dans son état actuel, fibre mise à jour ou fibre en développement.
La fibre actuelle est suivie par la fibre en cours de développement, puis, à son tour, la fibre est mise à jour.
L'état de fibre suivant est créé paresseusement à l'aide de la fonction cloneFiber. Presque toujours lors de la création d'un nouvel objet, cloneFiber tentera de réutiliser une alternative (paire) de fibres si elle existe, tout en minimisant le coût des ressources.
Vous devriez considérer le champ de vapeur (ou son alternative) comme un détail d'implémentation, mais il apparaît si souvent dans la documentation qu'il était tout simplement impossible de ne pas le mentionner.
La conclusion est un élément de service (ou un ensemble d'éléments de service); nœuds foliaires Réagissez aux applications. Ils sont spécifiques à chaque environnement d'affichage (par exemple, dans un navigateur, il s'agit de 'div', 'span', etc.). Dans JSX, ils sont désignés comme des noms de balises minuscules.
Conclusion: je recommande d'essayer les fonctionnalités de la nouvelle architecture React v16.0