J'ai un problème. L'application est écrite en angulaire et la bibliothèque de composants en React. Clonez une bibliothèque trop chère. Vous devez donc utiliser les composants React dans une application angulaire à un coût minimal. Nous trouvons comment le faire.
Clause de non-responsabilité
Je ne suis pas du tout spécialiste chez Angular. J'ai essayé la première version en 2017, puis j'ai regardé un peu AngularDart, et maintenant je suis tombé sur le support d'application sur une version moderne du framework. S'il vous semble que les décisions sont étranges ou "d'un autre monde", cela ne vous semble pas.
La solution présentée dans l'article n'a pas encore été testée dans des projets réels et n'est qu'un concept. Utilisez-le à vos risques et périls.
Le problème
Je supporte et développe maintenant une application assez grande sur Angular 8. De plus, il y a quelques petites applications sur React et prévoit d'en construire une douzaine d'autres (également sur React). Toutes les applications sont utilisées pour les besoins internes de l'entreprise et doivent être du même style. La seule façon logique est de créer une bibliothèque de composants sur la base de laquelle vous pouvez rapidement créer n'importe quelle application.
Mais dans la situation actuelle, vous ne pouvez pas simplement prendre et écrire une bibliothèque sur React. Vous ne pouvez pas l'utiliser dans la plus grande application - il est écrit en angulaire. Je ne suis pas le premier à rencontrer ce problème. La première chose à être sur Google pour "réagir en angulaire" est le référentiel Microsoft . Malheureusement, cette solution ne contient aucune documentation. De plus, dans le fichier Lisezmoi du projet, il est clairement indiqué que le package est utilisé en interne par Microsoft, l'équipe n'a aucun plan évident pour son développement et ce n'est qu'à vos risques et périls de l'utiliser. Je ne suis pas prêt à faire glisser un paquet aussi ambigu dans la production, j'ai donc décidé d'écrire mon vélo.

Site officiel @ angular-react / core
Solution
React est une bibliothèque conçue pour résoudre un problème spécifique - la gestion de l'arborescence DOM d'un document. Nous pouvons placer n'importe quel élément React dans un nœud DOM arbitraire.
const Hello = () => <p>Hello, React!</p>; render(<Hello />, document.getElementById('#hello'));
De plus, il n'y a aucune restriction sur les appels répétés à la fonction de render
. Autrement dit, il est possible de rendre chaque composant séparément dans le DOM. Et c'est exactement ce qui aidera à franchir la première étape de l'intégration.
La préparation
Tout d'abord, il est important de comprendre que React par défaut ne nécessite aucune condition particulière lors de l'assemblage. Si vous n'utilisez pas JSX, mais createElement
à appeler createElement
, vous n'aurez pas à prendre de mesures, tout fonctionnera tout seul.
Mais, bien sûr, nous sommes habitués à utiliser JSX et nous ne voulons pas le perdre. Heureusement, Angular utilise TypeScript par défaut, qui peut transformer JSX en appels de fonction. Il vous suffit d'ajouter l'indicateur du compilateur --jsx=react
ou dans tsconfig.json
dans la section compilerOptions
, ajoutez la ligne "jsx": "react"
.
Intégration d'affichage
Pour commencer, nous devons nous assurer que les composants React sont affichés dans l'application Angular. Autrement dit, pour que les éléments DOM résultants du travail de bibliothèque occupent les bons emplacements dans l'arborescence des éléments.
Chaque fois que vous utilisez le composant React, penser à appeler correctement la fonction de render
est trop difficile. De plus, à l'avenir, nous devrons intégrer des composants au niveau des données et des gestionnaires d'événements. Dans ce cas, il est logique de créer un composant angulaire qui encapsulera toute la logique de création et de contrôle de l'élément React.
Le code du composant Angular est extrêmement simple. Il ne rend lui-même qu'un conteneur vide et obtient un lien vers celui-ci. Dans ce cas, au moment de l'initialisation, le rendu de l'élément React est appelé. Il est créé à l'aide de la fonction createElement
et transmis à la fonction de render
, qui le place dans le nœud DOM créé à partir d'Angular. Vous pouvez utiliser un tel composant comme tout autre composant Angulat, sans conditions particulières.
Intégration d'entrée
Habituellement, lors de l'affichage des éléments d'interface, vous devez leur transférer des données. Tout ici est également assez prosaïque - lorsque vous appelez createElement
vous pouvez transmettre toutes les données via les accessoires au composant.
Vous pouvez maintenant passer la chaîne de name
au composant angulaire, elle tombera dans le composant React et sera rendue. Mais si la ligne change pour des raisons externes, React n'en sera pas informé et nous obtiendrons un affichage obsolète. Angular a une ngOnChanges
cycle de vie ngOnChanges
qui vous permet de suivre les changements dans les paramètres des composants et leurs réactions. Nous implémentons l'interface OnChanges
et ajoutons une méthode:
Il suffit d'appeler à nouveau la fonction de render
avec un élément créé à partir de nouveaux accessoires, et la bibliothèque elle-même déterminera quelles parties de l'arborescence doivent être rendues. L'état local à l'intérieur du composant sera également conservé.
Après ces manipulations, le composant angulaire peut être utilisé de la manière habituelle et lui transmettre des données.
<app-hello name="Angular"></app-hello> <app-hello [name]="name"></app-hello>
Pour un travail plus fin avec la mise à jour des composants, vous pouvez regarder vers la stratégie de détection des changements . Je ne l'examinerai pas en détail.
Intégration de sortie
Un autre problème demeure - la réaction de l'application aux événements à l'intérieur des composants React. Utilisons @Output
décorateur @Output
et passons le rappel au composant via des accessoires.
C'est fait. Lorsque vous utilisez le composant, vous pouvez enregistrer des gestionnaires d'événements et y répondre.
<app-hello [name]="name" (click)="sayHello($event)"></app-hello>
Le résultat est un wrapper entièrement fonctionnel pour le composant React à utiliser dans une application angulaire. Il peut transmettre des données et répondre aux événements qu'il contient.
Encore une chose ...
Pour moi, la chose la plus pratique dans Angular est la liaison de données bidirectionnelle, ngModel
. Il est pratique, simple, nécessite une très petite quantité de code. Mais dans l'implémentation actuelle, l'intégration n'est pas possible. Cela peut être corrigé. Pour être honnête, je ne comprends pas vraiment comment ce mécanisme fonctionne du point de vue de l'appareil interne. Par conséquent, j'avoue que ma solution est super-sous-optimale et je serai heureux si vous écrivez dans les commentaires une manière plus belle de prendre en charge ngModel
.
Tout d'abord, vous devez implémenter l'interface ControlValueAccessor
(à partir du package @angular/forms
et ajouter un nouveau fournisseur au composant.
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; const REACT_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => HelloComponent), multi: true }; @Component({ selector: 'app-hello', template: `<div #react></div>`, providers: [PREACT_VALUE_ACCESSOR], }) export class PreactComponent implements OnInit, OnChanges, ControlValueAccessor {
Cette interface nécessite l'implémentation des méthodes onBlur
, writeValue
, registerOnChange
, registerOnTouched
. Tous sont bien décrits dans la documentation. Nous les réalisons.
Après cela, vous devez vous assurer que tout cela est transmis au composant React. Malheureusement, React n'est pas en mesure de fonctionner avec la liaison de données bidirectionnelle, nous lui donnerons donc une valeur et un rappel pour le modifier. renderReactElement
méthode renderReactElement
.
Et dans le composant React, nous utiliserons cette valeur et le rappel.
export const Hello = ({ name, onClick, model }) => ( <div> <p>Hello, {name}!</p> <button onClick={onClick}>Say "hello"</button> <input value={model.value} onChange={e => model.onChange(e.target.value)} /> </div> );
Maintenant, nous avons vraiment intégré React dans Angular. Vous pouvez utiliser le composant résultant comme vous le souhaitez.
<app-hello [name]="name" (click)="sayHello($event)" [(ngModel)]="name" ></app-hello>
Résumé
React est une bibliothèque très simple qui est facile à intégrer avec n'importe quoi. En utilisant l'approche illustrée, vous pouvez non seulement utiliser de façon continue des composants React dans des applications angulaires, mais également migrer progressivement l'ensemble de l'application.
Dans cet article, je n'ai pas du tout abordé les problèmes de style. Si vous utilisez des solutions CSS-in-JS classiques (composants de style, émotion, JSS), vous n'avez aucune action supplémentaire à effectuer. Mais si le projet nécessite des solutions plus productives (astroturf, linaria, modules CSS), vous devrez travailler sur la configuration du webpack. Article de fond - Personnalisez la configuration de Webpack dans votre application angulaire .
Pour migrer complètement l'application d'Angular vers React, vous devez toujours résoudre le problème d'implémentation des services dans les composants React. Un moyen simple consiste à obtenir le service dans un composant wrapper et à le passer par des accessoires. La difficulté est d'écrire une couche qui obtiendra les services de l'injecteur par jeton. Examen de cette question au-delà de la portée de l'article.
Bonus
Il est important de comprendre qu'avec cette approche de 85 Ko d'Angular, près de 40 Ko de code React et React react-dom
sont react
. Cela peut avoir un impact significatif sur la vitesse de l'application. Je recommande d'envisager d'utiliser le Preact miniature, qui ne pèse que 3 Ko. Son intégration n'est presque pas différente.
- Dans
tsconfig.json
ajoutons une nouvelle option de compilation - "jsxFactory": "h"
, cela indiquera que vous devez utiliser la fonction h
pour convertir JSX. Maintenant, dans chaque fichier avec le code JSX - import { h } from 'preact'
. - Tous les appels à
React.createElement
remplacés par Preact.h
. - Tous les appels à
ReactDOM.render
remplacés par Preact.render
.
C'est fait! Lisez les instructions de migration de React vers Preact . Il n'y a pratiquement aucune différence.
UPD 19.9.19 16.49
Lien thématique à partir des commentaires - Micro Frontends
UPD 20.9.19 14.30
Un autre lien thématique des commentaires - Micro Frontends