Νοημοσύνη δεδομένων Πλάτωνα.
Κάθετη Αναζήτηση & Αι.

Δημιουργία διαλειτουργικών στοιχείων Ιστού που λειτουργούν ακόμη και με το React

Ημερομηνία:

Όσοι από εμάς είμαστε προγραμματιστές ιστού για περισσότερα από μερικά χρόνια, πιθανότατα έχουμε γράψει κώδικα χρησιμοποιώντας περισσότερα από ένα πλαίσια JavaScript. Με όλες τις επιλογές εκεί έξω - React, Svelte, Vue, Angular, Solid - είναι όλα αλλά αναπόφευκτα. Ένα από τα πιο απογοητευτικά πράγματα που πρέπει να αντιμετωπίσουμε όταν εργαζόμαστε σε διάφορα πλαίσια είναι η εκ νέου δημιουργία όλων αυτών των στοιχείων διεπαφής χρήστη χαμηλού επιπέδου: κουμπιά, καρτέλες, αναπτυσσόμενα μενού κ.λπ. Αυτό που είναι ιδιαίτερα απογοητευτικό είναι ότι συνήθως τα ορίζουμε σε ένα πλαίσιο , ας πούμε React, αλλά μετά πρέπει να τα ξαναγράψουμε αν θέλουμε να φτιάξουμε κάτι στο Svelte. Ή Vue. Ή Στερεά. Και ούτω καθεξής.

Δεν θα ήταν καλύτερα αν μπορούσαμε να ορίσουμε αυτά τα στοιχεία διεπαφής χρήστη χαμηλού επιπέδου μία φορά, με αγνωστικιστικό τρόπο πλαισίου και μετά να τα χρησιμοποιήσουμε ξανά μεταξύ πλαισίων; Φυσικά και θα έκανε! Και μπορούμε. τα στοιχεία Ιστού είναι ο τρόπος. Αυτή η ανάρτηση θα σας δείξει πώς.

Προς το παρόν, η ιστορία SSR για τα στοιχεία Ιστού λείπει λίγο. Το δηλωτικό σκιώδες DOM (DSD) είναι ο τρόπος με τον οποίο αποδίδεται ένα στοιχείο ιστού από την πλευρά του διακομιστή, αλλά, από τη στιγμή που γράφονται αυτές οι γραμμές, δεν είναι ενσωματωμένο με τα αγαπημένα σας πλαίσια εφαρμογών όπως το Next, το Remix ή το SvelteKit. Εάν αυτό είναι μια απαίτηση για εσάς, φροντίστε να ελέγξετε την πιο πρόσφατη κατάσταση του DSD. Αλλά διαφορετικά, εάν το SSR δεν είναι κάτι που χρησιμοποιείτε, διαβάστε παρακάτω.

Πρώτον, κάποιο πλαίσιο

Τα στοιχεία Ιστού είναι ουσιαστικά στοιχεία HTML που ορίζετε εσείς, όπως <yummy-pizza> ή οτιδήποτε άλλο, από τη βάση. Καλύπτονται παντού εδώ στο CSS-Tricks (συμπεριλαμβανομένων μια εκτενής σειρά από τον Caleb Williams και ένα από τον John Rhea) αλλά θα δούμε εν συντομία τη διαδικασία. Ουσιαστικά, ορίζετε μια κλάση JavaScript, την κληρονομείτε από αυτήν HTMLElementκαι, στη συνέχεια, ορίστε τις ιδιότητες, τα χαρακτηριστικά και τα στυλ που έχει το στοιχείο Ιστού και, φυσικά, τη σήμανση που θα αποδώσει τελικά στους χρήστες σας.

Το να μπορείτε να ορίσετε προσαρμοσμένα στοιχεία HTML που δεν συνδέονται με κάποιο συγκεκριμένο στοιχείο είναι συναρπαστικό. Αλλά αυτή η ελευθερία είναι επίσης ένας περιορισμός. Η ύπαρξη ανεξάρτητα από οποιοδήποτε πλαίσιο JavaScript σημαίνει ότι δεν μπορείτε πραγματικά να αλληλεπιδράσετε με αυτά τα πλαίσια JavaScript. Σκεφτείτε ένα στοιχείο React που ανακτά ορισμένα δεδομένα και στη συνέχεια αποδίδει μερικά άλλα Συνιστώσα React, περνώντας κατά μήκος των δεδομένων. Αυτό δεν θα λειτουργούσε πραγματικά ως στοιχείο Ιστού, καθώς ένα στοιχείο Ιστού δεν ξέρει πώς να αποδώσει ένα στοιχείο React.

Τα στοιχεία Ιστού υπερέχουν ιδιαίτερα ως συστατικά των φύλλων. Συστατικά φύλλων είναι το τελευταίο πράγμα που αποδίδεται σε ένα συστατικό δέντρο. Αυτά είναι τα στοιχεία που λαμβάνουν κάποια στηρίγματα και αποδίδουν μερικά UI. Αυτά είναι δεν τα στοιχεία που βρίσκονται στη μέση του δέντρου συστατικών σας, μεταβιβάζουν δεδομένα, ρυθμίζουν το πλαίσιο κ.λπ. — απλά κομμάτια UI θα φαίνεται το ίδιο, ανεξάρτητα από το πλαίσιο JavaScript που τροφοδοτεί την υπόλοιπη εφαρμογή.

Το στοιχείο ιστού που χτίζουμε

Αντί να φτιάξουμε κάτι βαρετό (και κοινό), όπως ένα κουμπί, ας φτιάξουμε κάτι λίγο διαφορετικό. Στο δικό μου φόρτωσης μήνυμα Εξετάσαμε τη χρήση προεπισκοπήσεων θολών εικόνων για να αποτρέψουμε την εκ νέου ροή περιεχομένου και να παρέχουμε μια αξιοπρεπή διεπαφή χρήστη για τους χρήστες κατά τη φόρτωση των εικόνων μας. Εξετάσαμε το base64 που κωδικοποιεί μια θολή, υποβαθμισμένες εκδόσεις των εικόνων μας και το δείχνει στη διεπαφή χρήστη μας κατά τη φόρτωση της πραγματικής εικόνας. Εξετάσαμε επίσης τη δημιουργία απίστευτα συμπαγών, θολών προεπισκοπήσεων χρησιμοποιώντας ένα εργαλείο που ονομάζεται Blurhash.

Αυτή η ανάρτηση σάς έδειξε πώς να δημιουργήσετε αυτές τις προεπισκοπήσεις και να τις χρησιμοποιήσετε σε ένα έργο React. Αυτή η ανάρτηση θα σας δείξει πώς να χρησιμοποιήσετε αυτές τις προεπισκοπήσεις από ένα στοιχείο ιστού, ώστε να μπορούν να χρησιμοποιηθούν από κάθε Πλαίσιο JavaScript.

Αλλά πρέπει να περπατήσουμε για να μπορέσουμε να τρέξουμε, οπότε θα δούμε πρώτα κάτι ασήμαντο και ανόητο για να δούμε ακριβώς πώς λειτουργούν τα στοιχεία Ιστού.

Όλα σε αυτήν την ανάρτηση θα δημιουργήσουν εξαρτήματα ιστού βανίλιας χωρίς κανένα εργαλείο. Αυτό σημαίνει ότι ο κωδικός θα έχει λίγο boilerplate, αλλά θα πρέπει να είναι σχετικά εύκολο να ακολουθηθεί. Εργαλεία όπως Lit or Πολυγραφώ έχουν σχεδιαστεί για την κατασκευή εξαρτημάτων ιστού και μπορούν να χρησιμοποιηθούν για την αφαίρεση μεγάλου μέρους αυτής της πλάκας λέβητα. Σας προτρέπω να τα ελέγξετε! Αλλά για αυτήν την ανάρτηση, θα προτιμήσω λίγο περισσότερο boilerplate με αντάλλαγμα να μην χρειάζεται να εισαγάγω και να διδάξω μια άλλη εξάρτηση.

Ένα απλό εξάρτημα μετρητή

Ας δημιουργήσουμε το κλασικό "Hello World" των στοιχείων JavaScript: έναν μετρητή. Θα αποδώσουμε μια τιμή και ένα κουμπί που αυξάνει αυτήν την τιμή. Απλό και βαρετό, αλλά θα μας επιτρέψει να δούμε το απλούστερο δυνατό στοιχείο Ιστού.

Για να δημιουργήσετε ένα στοιχείο Ιστού, το πρώτο βήμα είναι να δημιουργήσετε μια κλάση JavaScript, η οποία κληρονομεί από HTMLElement:

class Counter extends HTMLElement {}

Το τελευταίο βήμα είναι να καταχωρήσετε το στοιχείο web, αλλά μόνο εάν δεν το έχουμε ήδη καταχωρήσει:

if (!customElements.get("counter-wc")) {
  customElements.define("counter-wc", Counter);
}

Και, φυσικά, αποδώστε το:

<counter-wc></counter-wc>

Και όλα στο ενδιάμεσο κάνουν το στοιχείο web να κάνει ό,τι θέλουμε. Μια κοινή μέθοδος κύκλου ζωής είναι connectedCallback, το οποίο ενεργοποιείται όταν το στοιχείο ιστού μας προστίθεται στο DOM. Θα μπορούσαμε να χρησιμοποιήσουμε αυτήν τη μέθοδο για να αποδώσουμε ό,τι περιεχόμενο θέλουμε. Θυμηθείτε, αυτή είναι μια κλάση JS που κληρονομείται από HTMLElement, που σημαίνει το δικό μας this Η τιμή είναι το ίδιο το στοιχείο του στοιχείου web, με όλες τις συνήθεις μεθόδους χειρισμού DOM που ήδη γνωρίζετε και αγαπάτε.

Το πιο απλό, θα μπορούσαμε να κάνουμε αυτό:

class Counter extends HTMLElement {
  connectedCallback() {
    this.innerHTML = "<div style='color: green'>Hey</div>";
  }
}

if (!customElements.get("counter-wc")) {
  customElements.define("counter-wc", Counter);
}

…το οποίο θα λειτουργήσει μια χαρά.

Η λέξη "hey" με πράσινο χρώμα.

Προσθήκη πραγματικού περιεχομένου

Ας προσθέσουμε κάποιο χρήσιμο, διαδραστικό περιεχόμενο. Χρειαζόμαστε ενα <span> για να κρατήσετε την τρέχουσα αριθμητική τιμή και α <button> για να αυξήσετε τον μετρητή. Προς το παρόν, θα δημιουργήσουμε αυτό το περιεχόμενο στον κατασκευαστή μας και θα το προσαρτήσουμε όταν το στοιχείο ιστού βρίσκεται πραγματικά στο DOM:

constructor() {
  super();
  const container = document.createElement('div');

  this.valSpan = document.createElement('span');

  const increment = document.createElement('button');
  increment.innerText = 'Increment';
  increment.addEventListener('click', () => {
    this.#value = this.#currentValue + 1;
  });

  container.appendChild(this.valSpan);
  container.appendChild(document.createElement('br'));
  container.appendChild(increment);

  this.container = container;
}

connectedCallback() {
  this.appendChild(this.container);
  this.update();
}

Εάν είστε πραγματικά αισιόδοξοι από τη μη αυτόματη δημιουργία DOM, θυμηθείτε ότι μπορείτε να ορίσετε innerHTML, ή ακόμα και να δημιουργήσετε ένα στοιχείο προτύπου μία φορά ως στατική ιδιότητα της κλάσης στοιχείων Ιστού, να το κλωνοποιήσετε και να εισαγάγετε τα περιεχόμενα για νέες παρουσίες στοιχείων ιστού. Υπάρχουν πιθανώς κάποιες άλλες επιλογές που δεν σκέφτομαι ή μπορείτε πάντα να χρησιμοποιήσετε ένα πλαίσιο στοιχείων ιστού όπως Lit or Πολυγραφώ. Αλλά για αυτήν την ανάρτηση, θα συνεχίσουμε να το κρατάμε απλό.

Προχωρώντας, χρειαζόμαστε μια ιδιότητα κλάσης JavaScript με δυνατότητα ρύθμισης με όνομα value

#currentValue = 0;

set #value(val) {
  this.#currentValue = val;
  this.update();
}

Είναι απλώς μια τυπική ιδιότητα κλάσης με ρυθμιστή, μαζί με μια δεύτερη ιδιότητα που διατηρεί την τιμή. Μια διασκεδαστική ανατροπή είναι ότι χρησιμοποιώ τη σύνταξη ιδιοτήτων κλάσης ιδιωτικής JavaScript για αυτές τις τιμές. Αυτό σημαίνει ότι κανείς εκτός του στοιχείου ιστού μας δεν μπορεί ποτέ να αγγίξει αυτές τις τιμές. Αυτό είναι τυπικό JavaScript που υποστηρίζεται σε όλα τα σύγχρονα προγράμματα περιήγησης, οπότε μην φοβάστε να το χρησιμοποιήσετε.

Ή μη διστάσετε να το καλέσετε _value αν προτιμάς. Και, τέλος, το δικό μας update μέθοδος:

update() {
  this.valSpan.innerText = this.#currentValue;
}

Δουλεύει!

Προφανώς αυτός δεν είναι κώδικας που θα θέλατε να διατηρήσετε σε κλίμακα. Εδώ είναι ένα πλήρες παράδειγμα εργασίας αν θέλετε μια πιο προσεκτική ματιά. Όπως είπα, εργαλεία όπως το Lit και το Stencil έχουν σχεδιαστεί για να το κάνουν πιο απλό.

Προσθέτοντας κάποια ακόμη λειτουργικότητα

Αυτή η ανάρτηση δεν είναι μια βαθιά κατάδυση σε στοιχεία ιστού. Δεν θα καλύψουμε όλα τα API και τους κύκλους ζωής. δεν θα καλύψουμε καν σκιώδεις ρίζες ή υποδοχές. Υπάρχει ατελείωτο περιεχόμενο σε αυτά τα θέματα. Ο στόχος μου εδώ είναι να παρέχω μια αρκετά αξιοπρεπή εισαγωγή για να κεντρίσω κάποιο ενδιαφέρον, μαζί με κάποιες χρήσιμες οδηγίες για την πραγματικότητα χρησιμοποιώντας στοιχεία ιστού με τα δημοφιλή πλαίσια JavaScript που ήδη γνωρίζετε και αγαπάτε.

Για το σκοπό αυτό, ας βελτιώσουμε λίγο το στοιχείο ιστού του μετρητή μας. Ας το δεχθούμε α color χαρακτηριστικό, για να ελέγξετε το χρώμα της τιμής που εμφανίζεται. Και ας δεχτεί επίσης ένα increment ιδιότητα, επομένως οι καταναλωτές αυτού του στοιχείου ιστού μπορούν να το αυξήσουν κατά 2, 3, 4 κάθε φορά. Και για να οδηγήσουμε αυτές τις αλλαγές κατάστασης, ας χρησιμοποιήσουμε τον νέο μας μετρητή σε ένα Svelte sandbox — θα φτάσουμε στο React σε λίγο.

Θα ξεκινήσουμε με το ίδιο στοιχείο ιστού όπως πριν και θα προσθέσουμε ένα χαρακτηριστικό χρώματος. Για να διαμορφώσουμε το στοιχείο web μας ώστε να αποδέχεται και να ανταποκρίνεται σε ένα χαρακτηριστικό, προσθέτουμε ένα στατικό observedAttributes ιδιότητα που επιστρέφει τα χαρακτηριστικά που ακούει το στοιχείο ιστού μας.

static observedAttributes = ["color"];

Με αυτό στη θέση του, μπορούμε να προσθέσουμε ένα attributeChangedCallback μέθοδος κύκλου ζωής, η οποία θα εκτελείται όποτε ένα από τα χαρακτηριστικά που αναφέρονται observedAttributes έχουν οριστεί ή ενημερωθεί.

attributeChangedCallback(name, oldValue, newValue) {
  if (name === "color") {
    this.update();
  }
}

Τώρα ενημερώνουμε το δικό μας update μέθοδος για να το χρησιμοποιήσετε πραγματικά:

update() {
  this.valSpan.innerText = this._currentValue;
  this.valSpan.style.color = this.getAttribute("color") || "black";
}

Τέλος, ας προσθέσουμε το δικό μας increment ιδιοκτησία:

increment = 1;

Απλό και ταπεινό.

Χρησιμοποιώντας το στοιχείο μετρητή στο Svelte

Ας χρησιμοποιήσουμε αυτό που μόλις φτιάξαμε. Θα μπούμε στο στοιχείο της εφαρμογής Svelte και θα προσθέσουμε κάτι σαν αυτό:

<script>
  let color = "red";
</script>

<style>
  main {
    text-align: center;
  }
</style>

<main>
  <select bind:value={color}>
    <option value="red">Red</option>
    <option value="green">Green</option>
    <option value="blue">Blue</option>
  </select>

  <counter-wc color={color}></counter-wc>
</main>

Και δουλεύει! Ο μετρητής μας αποδίδει, αυξάνει και το αναπτυσσόμενο μενού ενημερώνει το χρώμα. Όπως μπορείτε να δείτε, αποδίδουμε το χαρακτηριστικό χρώμα στο πρότυπό μας Svelte και, όταν αλλάξει η τιμή, το Svelte χειρίζεται το έργο της κλήσης setAttribute στην υποκείμενη παρουσία μας στοιχείου ιστού. Δεν υπάρχει τίποτα ιδιαίτερο εδώ: αυτό είναι το ίδιο πράγμα που κάνει ήδη για τα χαρακτηριστικά του κάθε στοιχείο HTML.

Τα πράγματα γίνονται λίγο ενδιαφέροντα με το increment στήριγμα. Αυτό είναι δεν ένα χαρακτηριστικό στο στοιχείο ιστού μας. είναι ένα στήριγμα στην κατηγορία του στοιχείου web. Αυτό σημαίνει ότι πρέπει να οριστεί στην παρουσία του στοιχείου web. Υπομονή, καθώς τα πράγματα θα είναι πολύ πιο απλά σε λίγο.

Αρχικά, θα προσθέσουμε μερικές μεταβλητές στο στοιχείο Svelte:

let increment = 1;
let wcInstance;

Το εργοστάσιο παραγωγής ενέργειας ενός μετρητή στοιχείου θα σας επιτρέψει να αυξήσετε κατά 1 ή κατά 2:

<button on:click={() => increment = 1}>Increment 1</button>
<button on:click={() => increment = 2}>Increment 2</button>

Όμως, θεωρητικά, πρέπει να λάβουμε την πραγματική παρουσία του στοιχείου ιστού μας. Αυτό είναι το ίδιο πράγμα που κάνουμε πάντα κάθε φορά που προσθέτουμε ένα ref με το React. Με το Svelte, είναι απλό bind:this διευθυντικός:

<counter-wc bind:this={wcInstance} color={color}></counter-wc>

Τώρα, στο πρότυπό μας Svelte, ακούμε για αλλαγές στη μεταβλητή αύξησης του στοιχείου μας και ορίζουμε την υποκείμενη ιδιότητα στοιχείου web.

$: {
  if (wcInstance) {
    wcInstance.increment = increment;
  }
}

Μπορείτε να το δοκιμάσετε σε αυτό το live demo.

Προφανώς δεν θέλουμε να το κάνουμε αυτό για κάθε στοιχείο ιστού ή στήριγμα που πρέπει να διαχειριστούμε. Δεν θα ήταν ωραίο να μπορούσαμε να ρυθμίσουμε increment απευθείας στο στοιχείο ιστού μας, στη σήμανση, όπως κάνουμε συνήθως για τα στηρίγματα στοιχείων, και να το έχετε, ξέρετε, απλά δουλειά? Με άλλα λόγια, θα ήταν ωραίο να μπορούσαμε να διαγράψουμε όλες τις χρήσεις του wcInstance και χρησιμοποιήστε αυτόν τον απλούστερο κώδικα αντ' αυτού:

<counter-wc increment={increment} color={color}></counter-wc>

Αποδεικνύεται ότι μπορούμε. Αυτός ο κώδικας λειτουργεί. Η Svelte χειρίζεται όλο αυτό το legwork για εμάς. Δείτε το σε αυτό το demo. Αυτή είναι η τυπική συμπεριφορά για σχεδόν όλα τα πλαίσια JavaScript.

Γιατί λοιπόν σας έδειξα τον μη αυτόματο τρόπο ρύθμισης του στηρίγματος του στοιχείου web; Δύο λόγοι: είναι χρήσιμο να κατανοήσουμε πώς λειτουργούν αυτά τα πράγματα και, πριν από λίγο, είπα ότι αυτό λειτουργεί "σχεδόν" για όλα τα πλαίσια JavaScript. Αλλά υπάρχει ένα πλαίσιο το οποίο, παραδόξως, δεν υποστηρίζει τη ρύθμιση υποστηρικτικού στοιχείου ιστού όπως είδαμε μόλις.

Το React είναι ένα διαφορετικό θηρίο

Αντιδρώ. Το πιο δημοφιλές πλαίσιο JavaScript στον πλανήτη δεν υποστηρίζει βασική αλληλεπίδραση με στοιχεία Ιστού. Αυτό είναι ένα πολύ γνωστό πρόβλημα που είναι μοναδικό στο React. Είναι ενδιαφέρον ότι στην πραγματικότητα αυτό διορθώθηκε στον πειραματικό κλάδο του React, αλλά για κάποιο λόγο δεν συγχωνεύτηκε στην έκδοση 18. Τούτου λεχθέντος, μπορούμε ακόμα παρακολουθήστε την πρόοδό του. Και μπορείτε να το δοκιμάσετε μόνοι σας με ένα ζωντανή επίδειξη.

Η λύση, φυσικά, είναι να χρησιμοποιήσετε α ref, πιάστε την παρουσία του στοιχείου web και ορίστε μη αυτόματα increment όταν αυτή η τιμή αλλάζει. Μοιάζει με αυτό:

import React, { useState, useRef, useEffect } from 'react';
import './counter-wc';

export default function App() {
  const [increment, setIncrement] = useState(1);
  const [color, setColor] = useState('red');
  const wcRef = useRef(null);

  useEffect(() => {
    wcRef.current.increment = increment;
  }, [increment]);

  return (
    <div>
      <div className="increment-container">
        <button onClick={() => setIncrement(1)}>Increment by 1</button>
        <button onClick={() => setIncrement(2)}>Increment by 2</button>
      </div>

      <select value={color} onChange={(e) => setColor(e.target.value)}>
        <option value="red">Red</option>
        <option value="green">Green</option>
        <option value="blue">Blue</option>
      </select>

      <counter-wc ref={wcRef} increment={increment} color={color}></counter-wc>
    </div>
  );
}

Όπως συζητήσαμε, η μη αυτόματη κωδικοποίηση για κάθε ιδιότητα στοιχείου ιστού απλά δεν είναι επεκτάσιμη. Αλλά δεν χάθηκαν όλα γιατί έχουμε μερικές επιλογές.

Επιλογή 1: Χρησιμοποιήστε χαρακτηριστικά παντού

Έχουμε ιδιότητες. Εάν κάνατε κλικ στην παραπάνω επίδειξη του React, το increment Το στήριγμα δεν λειτουργούσε, αλλά το χρώμα άλλαξε σωστά. Δεν μπορούμε να κωδικοποιήσουμε τα πάντα με ιδιότητες; Δυστυχώς όχι. Οι τιμές των χαρακτηριστικών μπορούν να είναι μόνο συμβολοσειρές. Αυτό είναι αρκετά καλό εδώ, και θα μπορούσαμε να φτάσουμε κάπως μακριά με αυτήν την προσέγγιση. Αριθμοί όπως increment μπορεί να μετατραπεί σε και από συμβολοσειρές. Θα μπορούσαμε ακόμη και JSON να δεσμεύουμε/αναλύουμε αντικείμενα. Αλλά τελικά θα χρειαστεί να περάσουμε μια συνάρτηση σε ένα στοιχείο ιστού και σε αυτό το σημείο δεν θα είχαμε επιλογές.

Επιλογή 2: Τυλίξτε το

Υπάρχει ένα παλιό ρητό ότι μπορείτε να λύσετε οποιοδήποτε πρόβλημα στην επιστήμη των υπολογιστών προσθέτοντας ένα επίπεδο έμμεσης κατεύθυνσης (εκτός από το πρόβλημα των πάρα πολλών επιπέδων έμμεσης κατεύθυνσης). Ο κώδικας για να ορίσετε αυτά τα στηρίγματα είναι αρκετά προβλέψιμος και απλός. Κι αν το κρύψουμε σε μια βιβλιοθήκη; Οι έξυπνοι άνθρωποι πίσω από τον Λιτ έχουν μια λύση. Αυτή η βιβλιοθήκη δημιουργεί ένα νέο στοιχείο React για εσάς αφού του δώσετε ένα στοιχείο web και απαριθμήσει τις ιδιότητες που χρειάζεται. Αν και είμαι έξυπνος, δεν είμαι οπαδός αυτής της προσέγγισης.

Αντί να αντιστοιχίσω ένα προς ένα των στοιχείων ιστού σε στοιχεία React που έχουν δημιουργηθεί με μη αυτόματο τρόπο, αυτό που προτιμώ είναι απλώς ένας React στοιχείο που περνάμε το στοιχείο web μας όνομα ετικέτας προς την (counter-wc στην περίπτωσή μας) — μαζί με όλα τα χαρακτηριστικά και τις ιδιότητες — και για να αποδώσει αυτό το στοιχείο το στοιχείο ιστού μας, προσθέστε το ref, στη συνέχεια υπολογίστε τι είναι ένα στήριγμα και τι είναι ένα χαρακτηριστικό. Αυτή είναι η ιδανική λύση κατά τη γνώμη μου. Δεν γνωρίζω βιβλιοθήκη που να το κάνει αυτό, αλλά θα πρέπει να είναι απλή η δημιουργία. Ας το χαρίσουμε!

Αυτή είναι η χρήση ψάχνουμε για:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

wcTag είναι το όνομα της ετικέτας στοιχείου ιστού. Τα υπόλοιπα είναι οι ιδιότητες και τα χαρακτηριστικά που θέλουμε να περάσουν μαζί μας.

Δείτε πώς φαίνεται η εφαρμογή μου:

import React, { createElement, useRef, useLayoutEffect, memo } from 'react';

const _WcWrapper = (props) => {
  const { wcTag, children, ...restProps } = props;
  const wcRef = useRef(null);

  useLayoutEffect(() => {
    const wc = wcRef.current;

    for (const [key, value] of Object.entries(restProps)) {
      if (key in wc) {
        if (wc[key] !== value) {
          wc[key] = value;
        }
      } else {
        if (wc.getAttribute(key) !== value) {
          wc.setAttribute(key, value);
        }
      }
    }
  });

  return createElement(wcTag, { ref: wcRef });
};

export const WcWrapper = memo(_WcWrapper);

Η πιο ενδιαφέρουσα γραμμή είναι στο τέλος:

return createElement(wcTag, { ref: wcRef });

Έτσι δημιουργούμε ένα στοιχείο στο React με δυναμικό όνομα. Στην πραγματικότητα, σε αυτό το React μεταφέρει συνήθως το JSX. Όλα τα div μας μετατρέπονται σε createElement("div") κλήσεις. Συνήθως δεν χρειάζεται να καλέσουμε απευθείας αυτό το API, αλλά είναι εκεί όταν το χρειαζόμαστε.

Πέρα από αυτό, θέλουμε να εκτελέσουμε ένα εφέ διάταξης και να κάνουμε βρόχο σε κάθε στηρίγματα που έχουμε περάσει στο στοιχείο μας. Πραγματοποιούμε αναζήτηση σε όλα και ελέγχουμε για να δούμε αν πρόκειται για ιδιοκτησία με ένα in ελέγξτε ότι ελέγχει το αντικείμενο παρουσίας του στοιχείου Ιστού καθώς και την αλυσίδα πρωτοτύπου του, η οποία θα συλλάβει τυχόν λήπτες/ρυθμιστές που καταλήγουν στο πρωτότυπο της κλάσης. Εάν δεν υπάρχει τέτοια ιδιότητα, θεωρείται ότι είναι χαρακτηριστικό. Και στις δύο περιπτώσεις, το ορίζουμε μόνο εάν η τιμή έχει πραγματικά αλλάξει.

Αν αναρωτιέστε γιατί χρησιμοποιούμε useLayoutEffect αντί του useEffect, είναι επειδή θέλουμε να εκτελέσουμε αμέσως αυτές τις ενημερώσεις πριν από την απόδοση του περιεχομένου μας. Επίσης, σημειώστε ότι δεν έχουμε πίνακα εξάρτησης στο δικό μας useLayoutEffect; Αυτό σημαίνει ότι θέλουμε να εκτελέσουμε αυτήν την ενημέρωση κάθε απόδοση. Αυτό μπορεί να είναι επικίνδυνο, καθώς το React τείνει να αποδίδει εκ νέου πολύ. Το βελτιώνω αυτό τυλίγοντας το όλο θέμα μέσα React.memo. Αυτή είναι ουσιαστικά η σύγχρονη εκδοχή του React.PureComponent, πράγμα που σημαίνει ότι το στοιχείο θα αποδοθεί εκ νέου μόνο εάν κάποιο από τα πραγματικά του στηρίγματα έχει αλλάξει — και ελέγχει εάν αυτό συνέβη μέσω ενός απλού ελέγχου ισότητας.

Ο μόνος κίνδυνος εδώ είναι ότι εάν μεταβιβάσετε ένα αντικείμενο αντικειμένου που μεταλλάσσετε απευθείας χωρίς να το εκχωρήσετε ξανά, τότε δεν θα δείτε τις ενημερώσεις. Αλλά αυτό είναι πολύ αποθαρρυντικό, ειδικά στην κοινότητα του React, οπότε δεν θα ανησυχούσα γι' αυτό.

Πριν προχωρήσω, θα ήθελα να πω κάτι τελευταίο. Μπορεί να μην είστε ευχαριστημένοι με την εμφάνιση της χρήσης. Και πάλι, αυτό το στοιχείο χρησιμοποιείται ως εξής:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

Συγκεκριμένα, μπορεί να μην σας αρέσει να μεταβιβάσετε το όνομα της ετικέτας στοιχείου ιστού στο <WcWrapper> συστατικό και προτιμήστε αντί του το @lit-labs/react παραπάνω πακέτο, το οποίο δημιουργεί ένα νέο μεμονωμένο στοιχείο React για κάθε στοιχείο web. Αυτό είναι απολύτως δίκαιο και θα σας συνιστούσα να χρησιμοποιήσετε ό,τι αισθάνεστε πιο άνετα. Αλλά για μένα, ένα πλεονέκτημα αυτής της προσέγγισης είναι ότι είναι εύκολο διαγράψετε. Εάν από κάποιο θαύμα το React συγχωνεύσει τον σωστό χειρισμό στοιχείων web από τον πειραματικό κλάδο του main αύριο, θα μπορείτε να αλλάξετε τον παραπάνω κωδικό από αυτό:

<WcWrapper wcTag="counter-wc" increment={increment} color={color} />

…σ 'αυτό:

<counter-wc ref={wcRef} increment={increment} color={color} />

Θα μπορούσατε πιθανώς ακόμη και να γράψετε ένα μόνο codemod για να το κάνετε αυτό παντού και μετά να το διαγράψετε <WcWrapper> εντελώς. Στην πραγματικότητα, ξύστε αυτό: μια καθολική αναζήτηση και αντικατάσταση με ένα RegEx πιθανότατα θα λειτουργούσε.

Η εφαρμογή

Ξέρω, φαίνεται ότι χρειάστηκε ένα ταξίδι για να φτάσω εδώ. Αν θυμάστε, ο αρχικός μας στόχος ήταν να πάρουμε τον κώδικα προεπισκόπησης εικόνας που εξετάσαμε στο δικό μου φόρτωσης μήνυμακαι μετακινήστε το σε ένα στοιχείο web, ώστε να μπορεί να χρησιμοποιηθεί σε οποιοδήποτε πλαίσιο JavaScript. Η έλλειψη σωστής διασύνδεσης του React πρόσθεσε πολλές λεπτομέρειες στο μείγμα. Αλλά τώρα που έχουμε μια αξιοπρεπή διαχείριση για το πώς να δημιουργήσουμε ένα στοιχείο Ιστού και να το χρησιμοποιήσουμε, η υλοποίηση θα είναι σχεδόν αντικλιμακωτή.

Θα αφήσω ολόκληρο το στοιχείο Ιστού εδώ και θα αναφέρω μερικά από τα ενδιαφέροντα κομμάτια. Αν θέλετε να το δείτε σε δράση, εδώ είναι ένα επίδειξη λειτουργίας. Θα κάνει εναλλαγή μεταξύ των τριών αγαπημένων μου βιβλίων στις τρεις αγαπημένες μου γλώσσες προγραμματισμού. Η διεύθυνση URL για κάθε βιβλίο θα είναι μοναδική κάθε φορά, επομένως μπορείτε να δείτε την προεπισκόπηση, αν και πιθανότατα θα θέλετε να ρυθμίσετε τα πράγματα στην καρτέλα DevTools Network για να δείτε πραγματικά τα πράγματα που λαμβάνουν χώρα.

Δείτε ολόκληρο τον κώδικα
class BookCover extends HTMLElement {
  static observedAttributes = ['url'];

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'url') {
      this.createMainImage(newValue);
    }
  }

  set preview(val) {
    this.previewEl = this.createPreview(val);
    this.render();
  }

  createPreview(val) {
    if (typeof val === 'string') {
      return base64Preview(val);
    } else {
      return blurHashPreview(val);
    }
  }

  createMainImage(url) {
    this.loaded = false;
    const img = document.createElement('img');
    img.alt = 'Book cover';
    img.addEventListener('load', () =&gt; {
      if (img === this.imageEl) {
        this.loaded = true;
        this.render();
      }
    });
    img.src = url;
    this.imageEl = img;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    const elementMaybe = this.loaded ? this.imageEl : this.previewEl;
    syncSingleChild(this, elementMaybe);
  }
}

Αρχικά, καταχωρούμε το χαρακτηριστικό που μας ενδιαφέρει και αντιδρούμε όταν αλλάζει:

static observedAttributes = ['url'];

attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'url') {
    this.createMainImage(newValue);
  }
}

Αυτό προκαλεί τη δημιουργία του στοιχείου εικόνας μας, το οποίο θα εμφανίζεται μόνο όταν φορτωθεί:

createMainImage(url) {
  this.loaded = false;
  const img = document.createElement('img');
  img.alt = 'Book cover';
  img.addEventListener('load', () => {
    if (img === this.imageEl) {
      this.loaded = true;
      this.render();
    }
  });
  img.src = url;
  this.imageEl = img;
}

Στη συνέχεια έχουμε την ιδιότητα προεπισκόπησης, η οποία μπορεί να είναι η συμβολοσειρά προεπισκόπησης base64 ή η δική μας blurhash πακέτο:

set preview(val) {
  this.previewEl = this.createPreview(val);
  this.render();
}

createPreview(val) {
  if (typeof val === 'string') {
    return base64Preview(val);
  } else {
    return blurHashPreview(val);
  }
}

Αυτό αναβάλλει σε οποιαδήποτε βοηθητική λειτουργία χρειαζόμαστε:

function base64Preview(val) {
  const img = document.createElement('img');
  img.src = val;
  return img;
}

function blurHashPreview(preview) {
  const canvasEl = document.createElement('canvas');
  const { w: width, h: height } = preview;

  canvasEl.width = width;
  canvasEl.height = height;

  const pixels = decode(preview.blurhash, width, height);
  const ctx = canvasEl.getContext('2d');
  const imageData = ctx.createImageData(width, height);
  imageData.data.set(pixels);
  ctx.putImageData(imageData, 0, 0);

  return canvasEl;
}

Και, τέλος, το δικό μας render μέθοδος:

connectedCallback() {
  this.render();
}

render() {
  const elementMaybe = this.loaded ? this.imageEl : this.previewEl;
  syncSingleChild(this, elementMaybe);
}

Και μερικές βοηθητικές μέθοδοι για να συνδέσετε τα πάντα μεταξύ τους:

export function syncSingleChild(container, child) {
  const currentChild = container.firstElementChild;
  if (currentChild !== child) {
    clearContainer(container);
    if (child) {
      container.appendChild(child);
    }
  }
}

export function clearContainer(el) {
  let child;

  while ((child = el.firstElementChild)) {
    el.removeChild(child);
  }
}

Είναι λίγο περισσότερο boilerplate από ό,τι θα χρειαζόμασταν αν το φτιάξουμε σε ένα πλαίσιο, αλλά το θετικό είναι ότι μπορούμε να το χρησιμοποιήσουμε ξανά σε όποιο πλαίσιο θέλουμε — αν και το React θα χρειαστεί ένα περιτύλιγμα προς το παρόν, όπως συζητήσαμε .

Απομεινάρια

Έχω ήδη αναφέρει το περιτύλιγμα της Lit's React. Αλλά αν διαπιστώσετε ότι χρησιμοποιείτε το Stencil, υποστηρίζει στην πραγματικότητα α ξεχωριστός αγωγός εξόδου μόνο για το React. Και οι καλοί άνθρωποι στη Microsoft έχουν επίσης δημιούργησε κάτι παρόμοιο με το περιτύλιγμα του Lit, επισυνάπτεται στη βιβλιοθήκη στοιχείων Fast web.

Όπως ανέφερα, όλα τα πλαίσια που δεν ονομάζονται React θα χειρίζονται τον ορισμό των ιδιοτήτων στοιχείων web για εσάς. Απλώς σημειώστε ότι ορισμένα έχουν κάποιες ιδιαίτερες συντακτικές γεύσεις. Για παράδειγμα, με το Solid.js, <your-wc value={12}> πάντα υποθέτει ότι value είναι μια ιδιότητα, την οποία μπορείτε να παρακάμψετε με ένα attr πρόθεμα, όπως <your-wc attr:value={12}>.

Ολοκληρώνοντας

Τα στοιχεία Ιστού είναι ένα ενδιαφέρον, συχνά υποχρησιμοποιούμενο μέρος του τοπίου ανάπτυξης ιστού. Μπορούν να σας βοηθήσουν να μειώσετε την εξάρτησή σας από οποιοδήποτε μεμονωμένο πλαίσιο JavaScript διαχειριζόμενη τη διεπαφή χρήστη σας ή τα στοιχεία "φύλλων". Ενώ η δημιουργία αυτών των στοιχείων web - σε αντίθεση με τα στοιχεία Svelte ή React - δεν θα είναι τόσο εργονομική, το θετικό είναι ότι θα είναι ευρέως επαναχρησιμοποιήσιμα.

spot_img

Τελευταία Νοημοσύνη

spot_img

Συνομιλία με μας

Γεια σου! Πώς μπορώ να σε βοηθήσω?