Översikt
Denna vecka lugnar du ner dig lite! Du samlar ihop dig, repeterar och fokuserar på hur komponenter kan samarbeta. Information om veckans upplägg annonseras löpande i kursens Zulip-kanal.
Stödjande inspelningar
Section titled “Stödjande inspelningar”Komposition av komponenter
Section titled “Komposition av komponenter”Presentation | Exempel - Todo Application
📕 Errata
- Introduktion
- Vad är en bra komponent Niklas?
- Komposition av komponenter
- Exempel - Todo application
- Designregler - Bubbling av event (se informationstext nedan)
- Nestling av komponenter
- Nestling av komponenter i normala DOMen
- Nestling av komponenter i shadow DOMen
- Designregler - Härma de inbyggda elementen
- Designregler - Konfigurering via attribut
- Designregler - Inkapsling
- Orkestrering - Kommunikation mellan komponenter
- Case study - Flygkalkylatorn
- Designregler - Fallera tyst
- Lyssnar på förändring med “slotchange”
- Avslutning
Vad kännetecknar en bra komponent?
Section titled “Vad kännetecknar en bra komponent?”En bra komponent är liten, gör en enda sak och har så få externa beroenden som möjligt – den vet så lite som möjligt om resten av applikationen. En hel applikation (till exempel Spotify) är inte en komponent, det är en applikation byggd av många komponenter. Samtidigt finns det knappast en undre gräns: en komponent kan i princip vara lika liten som ett enda tecken (en emoji-komponent nämns som exempel). Den vanliga fällan är inte att ha för många komponenter, utan tvärtom för få – att av bekvämlighet lägga för mycket ansvar i en och samma komponent istället för att bryta ut den, på samma sätt som upprepad kod ofta borde brytas ut till en funktion.
Frågan den här föreläsningen egentligen kretsar kring är inte hur man skriver en bra komponent, utan hur flera välavgränsade komponenter ska samverka och kommunicera i en större applikation – komposition av komponenter.
Två sätt att nästla komponenter
Section titled “Två sätt att nästla komponenter”Vanlig nästling (light DOM): barnelement skrivs rakt in i föräldraelementet i HTML, precis som en <ul> med <li>-element. Föräldrakomponenten styr bara var de här barnen visas i sin shadow-DOM, med hjälp av <slot>. Den som använder komponenten har fullt utifrån-kontroll över vilka barn som läggs till, tas bort eller ändras – exakt som med vanlig HTML.
Nästling genom shadow-DOM: föräldrakomponenten skapar och äger sina barnelement själv, inuti sin egen shadow-root (till exempel genom att importera och instansiera dem i sin template.innerHTML). Barnen är då helt dolda och otillgängliga för den som använder föräldrakomponenten utifrån – att lägga till eller ändra dem är förälderns ansvar, inte konsumentens.
Vilket mönster som passar beror på vad komponenten ska vara: en lista där konsumenten själv ska kunna lägga till och ta bort poster (light DOM, som en to-do-list med to-do-item-barn) eller en komponent med intern, komplex uppbyggnad som konsumenten aldrig ska behöva bry sig om (till exempel en videospelare med inbyggda kontroller). De två mönstren kan också kombineras i samma komponent.
Praktiskt spelar det roll för hur man väljer ut element i koden:
// Barn i light DOM (t.ex. slottade to-do-items) – vanlig queryn på thisthis.querySelectorAll('to-do-item')
// Barn i shadow-DOM (t.ex. interna delar av videospelaren) – via shadowRootthis.shadowRoot.querySelectorAll('element-b')document.querySelectorAll(...) ska aldrig användas för det här – då söks hela sidans DOM igenom, vilket både ger fel resultat om komponenten finns flera gånger på sidan och skapar ett dolt beroende till resten av dokumentet.
Efterlikna inbyggda HTML-element
Section titled “Efterlikna inbyggda HTML-element”Grundprincipen för hur en komponent bör kommunicera: efterlikna hur riktiga HTML-element redan fungerar.
- In i komponenten: konfigurera via attribut (samma sätt som
srcpå en<img>). Det här täcker ungefär 99 % av fallen. - Ut från komponenten: kommunicera genom att utlösa händelser (
dispatchEvent), samma sätt ett formulär talar om att det skickats iväg. - Publika metoder med returvärden är ett sekundärt, mer sällan använt alternativ – används framför allt när datan som ska skickas in är för komplex för ett attribut (arrayer, objekt) eller när komponenten behöver initieras i flera steg (en
initialize-metod som till exempel visar en laddningsanimation tills den är klar).
Ett viktigt tekniskt villkor för attribut: de är alltid strängar, oavsett vilket värde som skrivs i HTML-koden. Sätts time="100" måste komponenten själv parsa strängen till ett tal innan den kan användas som ett. Bara primitiva datatyper (tal, strängar, booleaner) hör hemma i attribut – att JSON.stringify:a ett helt objekt och skjuta in det som en sträng i ett attribut fungerar tekniskt, men är ett fulhack som bör undvikas; hör datan till den kategorin (arrayer, objekt) är en publik metod rätt väg istället.
Håll komponenten inkapslad
Section titled “Håll komponenten inkapslad”En komponent ska bara bry sig om det som finns inuti sig själv – aldrig sträcka sig ut i det vanliga dokumentet för att läsa eller ändra saker där. document.querySelector(...) inuti en komponents egen kod är ett tydligt varningstecken (ett bra sätt att code-reviewa detta: sök filerna efter document. – dyker det upp inuti en komponentklass bör man fråga sig varför). Ska en nod skapas används förvisso document.createElement(...) (det finns ingen this.createElement), men den skapade noden ska alltid läggas till inuti komponentens eget träd (this eller this.shadowRoot), aldrig skjutas ut någon annanstans i dokumentet.
Det enda vedertagna undantaget är <head> – att en komponent i undantagsfall lägger till en resurs där (till exempel ett externt skript den beror på) kan vara motiverat, men bör vägas mot alternativ som en dynamisk import() eller fetch av samma resurs, som inte kräver att komponenten petar i det globala dokumentet.
bubbles och composed – var går gränsen för en händelse?
Section titled “bubbles och composed – var går gränsen för en händelse?”En komponents interna händelser bör hållas inuti komponenten – yttervärlden ska inte behöva bry sig om eller råka fånga upp händelser som egentligen hör till en inre delkomponent, inbäddad i en annan komponents shadow-DOM.
I den genomgångna to-do-list/to-do-item-koden utlöser to-do-item en stateChanged-händelse med { bubbles: true } när kryssrutan togglas, och to-do-list fångar den genom en enda händelselyssnare på sig själv (händelsedelegering) istället för en lyssnare per to-do-item. Den relevanta gränsen går vid composed, inte vid bubbles:
bubbles: truelåter händelsen bubbla uppåt i DOM-trädet som vanligt, men den stannar vid en shadow-root-gräns.to-do-itemhär är slottad – den är alltså en del av den vanliga, yttre DOM:en (light DOM) snarare än inbäddad ito-do-lists egen shadow-DOM – vilket är varför bubblingen fungerar som tänkt i det här specifika fallet.composed: trueär det som faktiskt bör undvikas: det låter händelsen även passera förbi en shadow-root-gräns, och riskerar att läcka en komponents interna händelser till kod som inte borde behöva bry sig om dem.
Standardvärdet för både bubbles och composed är false. Att sätta bubbles: true är alltså inte i sig ett brott mot inkapslingsprincipen – det är composed: true som är den egentliga risken att se upp med.
Orkestrera utifrån, låt inte komponenter anropa varandra direkt
Section titled “Orkestrera utifrån, låt inte komponenter anropa varandra direkt”Två syskonkomponenter ska aldrig anropa metoder på varandra direkt. Gör de det uppstår ett hårt beroende – komponent A kan då aldrig användas utan att komponent B också finns, precis som två HTML-element aldrig automatiskt interagerar med varandra bara för att de står bredvid varandra i koden.
Samma princip som ren funktionskomposition (funktionA() returnerar ett värde som fångas upp utanför och skickas vidare till funktionB(värde), snarare än att funktionA anropar funktionB direkt) gäller komponenter: komponent A utför sin uppgift och utlöser en händelse när den är klar. Den logik som kopplar ihop A och B – lyssnar på A:s händelse, läser eventuell data ur event.detail, och därefter sätter ett attribut eller anropar en metod på B – hör hemma i ett tredje, överordnat lager (till exempel applikationens index.js), inte i någon av komponenterna själva.
Försök misslyckas tyst
Section titled “Försök misslyckas tyst”En komponent bör i första hand undvika att kasta okontrollerade undantag ut mot sin konsument – ett fel i en enskild komponent ska helst inte krascha hela applikationen. Kod som kan misslyckas (till exempel ett fetch-anrop) omges därför gärna av try/catch.
Regeln är dock inte absolut: ibland är det motiverade att faktiskt kasta undantaget vidare, till exempel just när ett fetch-anrop misslyckas, så att yttre kod kan fånga upp felet, visa ett begripligt felmeddelande för användaren, eller försöka självläka applikationen på något sätt. Målet är alltså inte “kasta aldrig”, utan att inte låta fel propagera okontrollerat.
try { const response = await window.fetch('https://thewrongurl.com')} catch (error) { // Kod för att misslyckas graciöst}Läsanvisningar
Section titled “Läsanvisningar”Denna vecka introduceras egentligen ingen ny teori, utan befäster snarare tidigare begrepp. Därav finns det inga direkta läsanvisningar.
Övningar
Section titled “Övningar”Under Övningsuppgifter hittar du övningsuppgifter som det är lämpligt att du arbetar med under veckan.
Handledning
Section titled “Handledning”Skicka gärna in frågor på förhand via formuläret som du når här:
Examinationsuppgift B2
Section titled “Examinationsuppgift B2”Fortsätt arbetet med examinationsuppgift B2.
Utmaningar
Section titled “Utmaningar”Det finns ett par utmaningar att titta på och fundera kring lösningar.
Studiefrågor
Section titled “Studiefrågor”Här är ett antal frågor som du kan försöka besvara och diskutera tillsammans med din AI-assistent. Tveka inte att lyfta någon av frågorna till handledningen för att fortsätta diskussionen med lärare och studiekamrater.
- Finns det best practice kring hur man jobbar med webbkomponenter?
- Kan man bygga webbkomponenter som består av andra webbkomponenter, så kallad komposition av webbkomponenter?
Här är en utskrift från hur det såg ut när jag diskuterade ovan frågor med min AI-assistent Ajj.
Här följer tips till hur du kan prioritera bland kursmaterialet för att komma igång med veckan.
- Jobba på med B2. Se till att komma långt denna veckan, nästa vecka är leverans.
- Titta på föreläsningen och se om det finns tips du kan dra nytta av för att strukturera din kod i B2.
