Ceux d'entre nous qui ont été développeurs Web depuis plus de quelques années ont probablement écrit du code en utilisant plus d'un framework JavaScript. Avec tous les choix - réagir, svelte, vue, angulaire, solide - c'est presque inévitable. L'une des choses les plus frustrantes que nous devons gérer lorsque nous travaillons dans tous les frameworks est de recréer tous ces composants de l'interface utilisateur de bas niveau: boutons, onglets, listes déroulantes, etc. Ce qui est particulièrement frustrant, c'est que nous les avons généralement définis dans un cadre, par exemple, mais nous devons ensuite les réécrire si nous voulons construire quelque chose en svelte. Ou vue. Ou solide. Et ainsi de suite.
Ne serait-il pas préférable que nous puissions définir ces composants d'interface utilisateur de bas niveau une fois, d'une manière agnostique à cadre, puis les réutiliser entre les cadres? Bien sûr, c'est le cas! Et nous pouvons; Les composants Web sont le chemin. Ce message vous montrera comment.
À l'heure actuelle, l'histoire SSR pour les composants Web fait un peu défaut. Decarative Shadow Dom (DSD) est de savoir comment un composant Web est rendu côté serveur, mais, à ce jour, il n'est pas intégré à vos frameworks d'application préférés comme Next, Remix ou Sveltekit. Si c'est une exigence pour vous, assurez-vous de vérifier le dernier statut de DSD. Mais sinon, si SSR n'est pas quelque chose que vous utilisez, lisez la suite.
Les composants Web sont essentiellement des éléments HTML que vous définissez vous-même, comme
Être capable de définir des éléments HTML personnalisés qui ne sont pas liés à un composant particulier est passionnant. Mais cette liberté est aussi une limitation. Exister indépendamment de tout cadre JavaScript signifie que vous ne pouvez pas vraiment interagir avec ces frameworks JavaScript. Pensez à un composant React qui récupère certaines données, puis rend un autre composant React, passant les données. Cela ne fonctionnerait pas vraiment comme un composant Web, car un composant Web ne sait pas comment rendre un composant React.
Les composants Web excellent particulièrement comme composants feuilles . Les composants des feuilles sont la dernière chose à être rendue dans un arbre de composant. Ce sont les composants qui reçoivent des accessoires et rendent une interface utilisateur. Ce ne sont pas les composants assis au milieu de votre arborescence de composants, passant des données, en définissant le contexte, etc. - juste des morceaux d'interface utilisateur purs qui se ressembleront, peu importe quel framework JavaScript alimente le reste de l'application.
Plutôt que de construire quelque chose d'ennuyeux (et commun), comme un bouton, construisons quelque chose d'un peu différent. Dans mon dernier article, nous avons envisagé d'utiliser des aperçus d'images floues pour empêcher le reflux de contenu et fournir une interface utilisateur décente pour les utilisateurs pendant que nos images se chargent. Nous avons regardé Base64 codant une versions floues et dégradées de nos images, et montrant cela dans notre interface utilisateur tandis que la vraie image était chargée. Nous avons également envisagé de générer des aperçus incroyablement compacts et flous à l'aide d'un outil appelé Blurhash.
Ce message vous a montré comment générer ces aperçus et les utiliser dans un projet React. Ce message vous montrera comment utiliser ces aperçus à partir d'un composant Web afin qu'ils puissent être utilisés par n'importe quel framework JavaScript.
Mais nous devons marcher avant de pouvoir courir, donc nous allons d'abord parcourir quelque chose de trivial et idiot pour voir exactement comment fonctionnent les composants Web.
Tout dans cet article créera des composants Web Vanilla sans aucun outillage. Cela signifie que le code aura un peu de passe-partout, mais devrait être relativement facile à suivre. Des outils comme allumé ou pochoir sont conçus pour créer des composants Web et peuvent être utilisés pour supprimer une grande partie de cette passe-partout. Je vous exhorte à les vérifier! Mais pour ce post, je préférerai un peu plus de passe-partout en échange de ne pas avoir à introduire et d'enseigner une autre dépendance.
Construisons le «Hello World» classique des composants JavaScript: un compteur. Nous rendrons une valeur et un bouton qui augmente cette valeur. Simple et ennuyeux, mais cela nous permettra de regarder le composant Web le plus simple possible.
Afin de créer un composant Web, la première étape consiste à faire une classe JavaScript, qui hérite de Htmlelement:
La classe Counter étend htmlelement {}
La dernière étape consiste à enregistrer le composant Web, mais seulement si nous ne l'avons pas déjà enregistré:
if (! CustomElements.get ("Counter-wc")) { CustomElements.define ("Counter-WC", Counter); }
Et, bien sûr, le rendez:
<cought-wc> Counter-WC></cought-wc>
Et tout ce qui est entre les deux, nous faisons faire le composant Web tout ce que nous voulons. Une méthode de cycle de vie courante est connectéCallback, qui tire lorsque notre composant Web est ajouté au DOM. Nous pourrions utiliser cette méthode pour rendre le contenu que nous aimerions. N'oubliez pas qu'il s'agit d'une classe JS héritant de HTMLelement, ce qui signifie que notre valeur est l'élément de composant Web lui-même, avec toutes les méthodes de manipulation DOM normales que vous connaissez et aimez déjà.
À son plus simple, nous pourrions faire ceci:
La classe Counter étend htmlelement { connectedCallback () { this.innerhtml = "<div style="'couleur:" vert> hey </div>"; } } if (! CustomElements.get ("Counter-wc")) { CustomElements.define ("Counter-WC", Counter); }
… Ce qui fonctionnera très bien.
Ajoutons un contenu interactif utile et interactif. Nous avons besoin d'un pour maintenir la valeur du nombre actuel et un
constructeur () { super(); const Container = document.CreateElement ('div'); this.valspan = document.createElement ('span'); const incrément = document.CreateElement ('bouton'); incment.innertext = 'incrément'; incment.addeventListener ('click', () => { this. # value = this. # currentValue 1; }); contener.ApendChild (this.valspan); contener.ApendChild (document.CreateElement ('BR')); conteneur.ApendChild (incrément); this.Container = conteneur; } connectedCallback () { this.ApendChild (this.Container); this.update (); }
Si vous êtes vraiment dégoûté par la création de DOM manuelle, n'oubliez pas que vous pouvez définir InnerHTML, ou même créer un élément de modèle une fois comme une propriété statique de votre classe de composants Web, le cloner et insérer le contenu des nouvelles instances de composants Web. Il y a probablement d'autres options auxquelles je ne pense pas, ou vous pouvez toujours utiliser un framework de composant Web comme Lit ou Pochch. Mais pour ce post, nous continuerons à rester simples.
En passant à autre chose, nous avons besoin d'une propriété de classe JavaScript établie nommée
#CurrentValue = 0; set #value (val) { this. # currentValue = val; this.update (); }
C'est juste une propriété de classe standard avec un secteur, ainsi qu'une deuxième propriété pour maintenir la valeur. Une torsion amusante est que j'utilise la syntaxe de propriété privée de la classe JavaScript pour ces valeurs. Cela signifie que personne en dehors de notre composant Web ne peut jamais toucher ces valeurs. Il s'agit d'un JavaScript standard qui est pris en charge dans tous les navigateurs modernes, alors n'ayez pas peur de l'utiliser.
Ou n'hésitez pas à l'appeler _value si vous préférez. Et, enfin, notre méthode de mise à jour:
mise à jour() { this.valspan.innertext = this. # currentValue; }
Ça marche!
De toute évidence, ce n'est pas du code que vous souhaitez maintenir à grande échelle. Voici un exemple de travail complet si vous souhaitez un examen plus approfondi. Comme je l'ai dit, des outils comme LIT et SPORCH sont conçus pour rendre cela plus simple.
Ce message n'est pas une plongée profonde dans les composants Web. Nous ne couvrirons pas toutes les API et les cycles de vie; Nous ne couvrirons même pas les racines ou les créneaux d'ombre. Il y a un contenu sans fin sur ces sujets. Mon objectif ici est de fournir une introduction suffisamment décente pour susciter un certain intérêt, ainsi que des conseils utiles sur l' utilisation de composants Web avec les frameworks JavaScript populaires que vous connaissez et aimez déjà.
À cette fin, améliorons un peu notre composant Web de compteur. Soyons-il accepter un attribut de couleur, pour contrôler la couleur de la valeur affichée. Et le faisons également accepter une propriété d'incrément, afin que les consommateurs de ce composant Web puissent le faire augmenter de 2, 3, 4 à la fois. Et pour générer ces modifications d'état, utilisons notre nouveau compteur dans un sandbox svelte - nous pourrons réagir un peu.
Nous allons commencer par le même composant Web qu'auparavant et ajouter un attribut de couleur. Pour configurer notre composant Web pour accepter et répondre à un attribut, nous ajoutons une propriété statique ObservedAttributes qui renvoie les attributs pour lesquels notre composant Web écoute.
statique observéAttributes = ["Color"];
Avec cela en place, nous pouvons ajouter une méthode de cycle de vie AttributeChangedCallback, qui s'exécutera chaque fois que l'un des attributs répertoriés dans observédattributes sera défini ou mis à jour.
AttributeChangedCallback (nom, OldValue, NewValue) { if (name === "Color") { this.update (); } }
Maintenant, nous mettons à jour notre méthode de mise à jour pour l'utiliser:
mise à jour() { this.valspan.innertext = this._currentValue; this.valspan.style.color = this.getAttribute ("Color") || "noir"; }
Enfin, ajoutons notre propriété d'incrément:
incrément = 1;
Simple et humble.
Utilisons ce que nous venons de faire. Nous allons aller dans notre composant d'application Svelte et ajouter quelque chose comme ceci:
<cript> Soit Color = "Red"; </cript> <style> principal { Texte-aligne: Centre; } </ style> <Main> <select bind: value = {couleur}> <Option Value = "Red"> Red <Option Value = "Green"> Green </ Option> <Option Value = "Blue"> Blue </ Option> <Counter-WC Color = {Color}> </ Counter-WC> </-main></style>
Et ça marche! Notre compteur rend, incréments et la liste déroulante met à jour la couleur. Comme vous pouvez le voir, nous rendons l'attribut Color dans notre modèle Svelte et, lorsque la valeur change, Svelte gère le travail des jambes SetAttribute sur notre instance de composant Web sous-jacente. Il n'y a rien de spécial ici: c'est la même chose qu'il fait déjà pour les attributs de n'importe quel élément HTML.
Les choses deviennent un peu intéressantes avec l'incrément prop. Ce n'est pas un attribut sur notre composant Web; C'est un accessoire sur la classe du composant Web. Cela signifie qu'il doit être défini sur l'instance du composant Web. Restez avec moi, car les choses finiront par un peu plus simples.
Tout d'abord, nous allons ajouter des variables à notre composant svelte:
Soit l'incrément = 1; Laissez WCinstance;
Notre puissance d'un compteur vous permettra d'incrémenter de 1, ou de 2:
<button sur: cliquez sur="{()"> incrément = 1}> incrément 1 bouton> <button sur: cliquez sur="{()"> incrément = 2}> incrément 2 </button></button>
Mais, en théorie , nous devons obtenir l'instance réelle de notre composant Web. C'est la même chose que nous faisons toujours chaque fois que nous ajoutons une référence avec React. Avec Svelte, c'est une simple liaison: cette directive:
<counter-wc bind: this="{wcinstance}" color="{colore}"> counter-wc></counter-wc>
Maintenant, dans notre modèle svelte, nous écoutons les modifications de la variable incrément de notre composant et définissons la propriété du composant Web sous-jacente.
$: { if (wcinstance) { wcinstance.increment = incrément; } }
Vous pouvez le tester à cette démo en direct.
Nous ne voulons évidemment pas le faire pour chaque composant Web ou un accessoire que nous devons gérer. Ne serait-il pas bien si nous pouvions simplement définir l'incrément directement sur notre composant Web, en balisage, comme nous le faisons normalement pour les accessoires de composants, et l'avons, vous savez, simplement travailler ? En d'autres termes, ce serait bien si nous pouvions supprimer tous les usages de WCinstance et utiliser ce code plus simple à la place:
<counter-wc incmenment="{incrément}" color="{Color}"> Counter-wc></counter-wc>
Il s'avère que nous pouvons. Ce code fonctionne; Svelte gère tout ce travail des jambes pour nous. Vérifiez-le dans cette démo. Il s'agit d'un comportement standard pour à peu près tous les frameworks JavaScript.
Alors pourquoi vous ai-je montré la façon manuelle de définir l'hélice du composant Web? Deux raisons: il est utile de comprendre comment ces choses fonctionnent et, il y a un instant, j'ai dit que cela fonctionne pour «à peu près» tous les cadres JavaScript. Mais il y a un cadre qui, exaspérément, ne prend pas en charge le paramètre d'accessoire de composant Web comme nous venons de le voir.
Réagir. Le framework JavaScript le plus populaire de la planète ne prend pas en charge l'interopération de base avec les composants Web. Il s'agit d'un problème bien connu qui est unique à réagir. Fait intéressant, cela est en fait fixé dans la branche expérimentale de React, mais pour une raison quelconque, n'a pas été fusionnée dans la version 18. Cela dit, nous pouvons toujours suivre la progression de celle-ci. Et vous pouvez l'essayer vous-même avec une démo en direct.
La solution, bien sûr, consiste à utiliser une réf., À saisir l'instance du composant Web et à définir manuellement l'incrément lorsque cette valeur change. Cela ressemble à ceci:
Importer React, {UseState, useref, useEffecte} de 'react'; import './counter-wc'; Exporter la fonction par défaut App () { const [incrément, setIncrement] = useState (1); const [couleur, setColor] = UseState («rouge»); const wcref = useRef (null); useEFFECT (() => { wcref.current.increment = incrément; }, [incrément]); retour ( <div> <div classname="incontage-contrainer"> <button onclick="{()"> setInCment (1)}> Incrément par 1 Button> <button onclick="{()"> setInCment (2)}> Incrément par 2 Button> </button></button> </div> <select value="{Color}" onchange="{(e)"> setColor (e.target.value)}> <option value="Red"> Red </option> <option value="Green"> Green Option> </option> <option value="Blue"> Blue Option> <counter-wc ref="{WCref}" incr color="{Color}"> Counter-wc> </counter-wc> </option></select> </div> )); }
Comme nous l'avons discuté, le codage manuellement pour chaque propriété de composant Web n'est tout simplement pas évolutif. Mais tout n'est pas perdu parce que nous avons quelques options.
Nous avons des attributs. Si vous avez cliqué sur la démo React ci-dessus, l'incrément ne fonctionnait pas, mais la couleur a correctement changé. Ne pouvons-nous pas tout coder avec des attributs? Malheureusement, non. Les valeurs d'attribut ne peuvent être que des chaînes. C'est assez bon ici, et nous pourrions aller un peu loin avec cette approche. Des nombres comme l'incrément peuvent être convertis vers et à partir des chaînes. Nous pourrions même les objets JSON Stringify / Parse. Mais finalement, nous devrons transmettre une fonction dans un composant Web, et à ce stade, nous serions hors d'options.
Il y a un vieux dicton que vous pouvez résoudre tout problème en informatique en ajoutant un niveau d'indirection (sauf le problème de trop de niveaux d'indirection). Le code pour définir ces accessoires est assez prévisible et simple. Et si nous le cachons dans une bibliothèque? Les gens intelligents derrière LIT ont une solution. Cette bibliothèque crée un nouveau composant React pour vous après lui avoir donné un composant Web et répertorier les propriétés dont il a besoin. Bien que intelligent, je ne suis pas fan de cette approche.
Plutôt que d'avoir une cartographie un à un des composants Web aux composants REACT créés manuellement, ce que je préfère n'est qu'un composant React à lequel nous passons notre nom de balise de composant Web (Counter-WC dans notre cas) - ainsi que tous les attributs et les propriétés - et pour ce composant pour rendre notre composant Web, ajouter la réf. C'est la solution idéale à mon avis. Je ne connais pas une bibliothèque qui fait cela, mais elle devrait être simple à créer. Donnons-y un coup!
C'est l' utilisation que nous recherchons:
<wcwrapper wctag="Counter-wc" incr color="{color}"></wcwrapper>
WCTAG est le nom de balise du composant Web; Les autres sont les propriétés et les attributs que nous voulons transmis.
Voici à quoi ressemble mon implémentation:
Importer React, {CreateElement, useRef, useLayoutEffECT, Memo} de 'react'; const _wcwrapper = (accessoires) => { const {wctag, enfants, ... restprops} = accessoires; const wcref = useRef (null); uselayOutEffEct (() => { const wc = wcref.current; for (const [key, valeur] de object.entries (restprops)) { if (clé dans wc) { if (wc [key]! == valeur) { wc [key] = valeur; } } autre { if (wc.GetAttribute (key)! == valeur) { wc.SetAttribute (clé, valeur); } } } }); return CreateElement (wctag, {ref: wcref}); }; export const wcwrapper = memo (_wcwrapper);
La ligne la plus intéressante est à la fin:
return CreateElement (wctag, {ref: wcref});
C'est ainsi que nous créons un élément de réaction avec un nom dynamique. En fait, c'est ce qui réagit normalement en JSX. Tous nos divs sont convertis en appels CreateElement ("div"). Nous n'avons normalement pas besoin d'appeler cette API directement, mais c'est là quand nous en avons besoin.
Au-delà de cela, nous voulons exécuter un effet de mise en page et une boucle à travers chaque accessoire que nous avons transmis à notre composant. Nous les traversons tous et vérifions pour voir s'il s'agit d'une propriété avec une vérification qui vérifie l'objet d'instance de composant Web ainsi que sa chaîne prototype, qui attrapera tous les getters / setters qui se retrouvent sur le prototype de classe. Si aucune propriété de ce type n'existe, elle est supposée être un attribut. Dans les deux cas, nous ne le définissons que si la valeur a réellement changé.
Si vous vous demandez pourquoi nous utilisons USELAYOINFEFFECT au lieu d'utiliser Effet, c'est parce que nous voulons immédiatement exécuter ces mises à jour avant que notre contenu ne soit rendu. Notez également que nous n'avons pas de tableau de dépendance à notre uselayouteffet; Cela signifie que nous voulons exécuter cette mise à jour sur chaque rendu . Cela peut être risqué puisque React a tendance à renvoyer beaucoup . J'améliore cela en enroulant le tout dans react.memo. Il s'agit essentiellement de la version moderne de React.PureComponent, ce qui signifie que le composant ne renforcera que si l'un de ses accessoires réels a changé - et il vérifie si cela s'est produit via un simple contrôle d'égalité.
Le seul risque ici est que si vous passez un accessoire d'objet que vous mutez directement sans réaffecter, vous ne verrez pas les mises à jour. Mais cela est très découragé, surtout dans la communauté React, donc je ne m'en inquiéterais pas.
Avant de continuer, j'aimerais appeler une dernière chose. Vous pourriez ne pas être satisfait de l'apparence de l'utilisation. Encore une fois, ce composant est utilisé comme ceci:
<wcwrapper wctag="Counter-wc" incr color="{color}"></wcwrapper>
Plus précisément, vous n'aimez peut-être pas passer le nom de balise du composant Web au composant
<wcwrapper wctag="Counter-wc" incr color="{color}"></wcwrapper>
… À ceci:
<counter-wc ref="{WCref}" incr color="{Color}"></counter-wc>
Vous pourriez probablement même écrire un seul Codemod pour ce faire partout, puis supprimer complètement
Je sais, il semble qu'il ait fallu un voyage pour arriver ici. Si vous vous souvenez, notre objectif d'origine était de prendre le code d'aperçu de l'image que nous avons examiné dans mon dernier article et de le déplacer vers un composant Web afin qu'il puisse être utilisé dans n'importe quel cadre JavaScript. Le manque d'interopération approprié de React a ajouté beaucoup de détails au mélange. Mais maintenant que nous avons une poignée décente sur la façon de créer un composant Web et de l'utiliser, la mise en œuvre sera presque anti-climatique.
Je vais laisser tomber l'intégralité du composant Web ici et appeler certains des bits intéressants. Si vous souhaitez le voir en action, voici une démo de travail. Il basculera entre mes trois livres préférés sur mes trois langages de programmation préférés. L'URL de chaque livre sera unique à chaque fois, vous pouvez donc voir l'aperçu, bien que vous voudrez probablement étrangler les choses dans votre onglet de réseau Devtools pour vraiment voir les choses se dérouler.
Class BookCover étend htmlelement { statique observéAttributes = ['url']; AttributeChangedCallback (nom, OldValue, NewValue) { if (name === 'url') { this.CreateMainImage (newValue); } } Définir l'aperçu (Val) { this.previewel = this.createPreview (val); this.render (); } createPreview (val) { if (typeof val === 'string') { return Base64Preview (Val); } autre { return blurhashPreview (Val); } } CreateMainImage (URL) { this.loaded = false; const img = document.createElement ('img'); img.alt = 'couverture de livres'; img.addeventListener ('Load', () = & gt; { if (img === this.imageel) { this.loaded = true; this.render (); } }); img.src = url; this.imageel = img; } connectedCallback () { this.render (); } rendre() { const elementMaybe = this.loaded? this.imageel: this.previewel; SyncSingleChild (this, elementMaybe); } }
Tout d'abord, nous enregistrons l'attribut qui nous intéresse et réagissons lorsqu'il change:
statique observéAttributes = ['url']; AttributeChangedCallback (nom, OldValue, NewValue) { if (name === 'url') { this.CreateMainImage (newValue); } }
Cela provoque la création de notre composant d'image, qui ne s'affichera que lorsqu'il est chargé:
CreateMainImage (URL) { this.loaded = false; const img = document.createElement ('img'); img.alt = 'couverture de livres'; img.addeventListener ('Load', () => { if (img === this.imageel) { this.loaded = true; this.render (); } }); img.src = url; this.imageel = img; }
Ensuite, nous avons notre propriété Aperçu, qui peut être notre chaîne d'aperçu Base64, soit notre paquet Blurhash:
Définir l'aperçu (Val) { this.previewel = this.createPreview (val); this.render (); } createPreview (val) { if (typeof val === 'string') { return Base64Preview (Val); } autre { return blurhashPreview (Val); } }
Cela se retire de la fonction d'assistance dont nous avons besoin:
function Base64Preview (Val) { const img = document.createElement ('img'); img.src = val; retour IMG; } fonction blurhashpreview (préview) { const canVasel = document.CreateElement ('Canvas'); const {w: width, h: height} = aperçu; canvasel.width = largeur; canvase.Height = hauteur; const pixels = decode (préview.blurhash, largeur, hauteur); const ctx = canvase.getContext ('2d'); const Imagedata = ctx.CreateImagedata (largeur, hauteur); iMagedata.data.set (pixels); ctx.putImagedata (imagedata, 0, 0); RETOUR CHANVASEL; }
Et, enfin, notre méthode de rendu:
connectedCallback () { this.render (); } rendre() { const elementMaybe = this.loaded? this.imageel: this.previewel; SyncSingleChild (this, elementMaybe); }
Et quelques méthodes d'aide pour tout lier ensemble:
Fonction d'exportation SyncSingleChild (conteneur, enfant) { const CurrentChild = contener.FirstelementChild; if (currentChild! == enfant) { ClearContainer (conteneur); if (enfant) { conteneur.ApendChild (enfant); } } } Fonction d'exportation ClearContainer (El) { Laissez l'enfant; while ((child = el.firstetementchild)) { El.removechild (enfant); } }
C'est un peu plus de chauffeur que nous n'enrivons que si nous le construisons dans un cadre, mais l'avantage est que nous pouvons réutiliser cela dans n'importe quel cadre que nous aimerions - bien que React aura besoin d'un wrapper pour l'instant, comme nous l'avons discuté.
J'ai déjà mentionné l'emballage React de Lit. Mais si vous vous retrouvez à utiliser du pochoir, il prend en charge un pipeline de sortie séparé juste pour React. Et les bons gens de Microsoft ont également créé quelque chose de similaire à l'emballage de Lit, attaché à la bibliothèque de composants Web Fast.
Comme je l'ai mentionné, tous les cadres non nommés react géreront la définition des propriétés des composants Web pour vous. Notez simplement que certains ont des saveurs spéciales de syntaxe. Par exemple, avec solide.js,
Les composants Web sont une partie intéressante, souvent sous-utilisée du paysage de développement Web. Ils peuvent aider à réduire votre dépendance à l'égard de tout cadre JavaScript unique en gérant vos composants d'interface utilisateur ou «feuilles». Tout en les créant en tant que composants Web - par opposition aux composants Svelte ou React - ne sera pas aussi ergonomique, la hausse est qu'elles seront largement réutilisables.
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!