Kuidas kirjutada nurgas 2+ häid, komposteeritavaid ja puhtaid komponente

Enamik meist teab, mis on nutikad ja lollid komponendid. Me teame, et peaksime kasutama nii palju kui võimalik @Input () ja @Output (). Kuid kui meie SPA saab piisavalt suureks, hakkab see meile üha enam meelde tuletama tüüpilist spagetti ja tundub, et me ei saa seda isegi aidata.

Põhjus on sageli see, et me teame, mis on koodide väljatöötamisel head ja halvad mustrid, kuid eriti Front-endi puhul läheme sageli segadusse sellega, mida me peaksime tegema ja mida me ei peaks tegema. Mustrid, mida peaksime järgima, hakkavad häguseks muutuma ja lõppkokkuvõttes kasutavad meie koodis otseteid sagedamini kui meil seda pole.

Üks sellistest mustritest on jagada oma komponendid targaks ja nukraks. Selles öeldakse, et me peaksime hoidma kogu äriloogikat ja kõrvalmõjusid nutikates komponentides, tehes kõik muud komponendid võimalikult lollideks.

See töötab hästi, kuid kui me ei rakenda oma projektis mõnda ranget reeglit, unustame selle. Sest kui muster ei ole reegel ja on lihtsalt "üldine idee", mida me meeles peame, siis lõpuks jätame selle üha enam vahele.

Näiteks hakkame oma komponente tundma, kuid kui tunneme ülespuhumist vajadusega kõiki sisendeid ja väljundeid kõikjal edastada ja käsitseda, muutume laisad ja muteerime sisendite andmeid otse.
(See muudab raskeks ära arvata, milline komponent muudab milliseid andmeid ja millal.)

Teinekord tahame oma komponentide jõudlust “optimeerida” ning sõltuvalt sisenditest ja väljunditest lähtume lihtsalt ng1 lähenemisviisist, mis tähendab, et loome üksiku kontrolleri teenuse, mille iga komponent süstib endale alati, kui see on vajalik vajab midagi.
(See raskendab selle otsustamist, millised komponendid milliseid andmeid saavad.)

Veel mõned juhtumid kiirklahvide võtmiseks ja hakkame märkama, et kummalisel kombel ei rakenda Nurga muutuste tuvastamise mehhanism meie komponentide muudatusi (kui tavaliselt peaks). Seega lisame siia-sinna paar this.cdRef.markForCheck () rida, et see töötaks.

Paar kuud edasi liikudes pole mitte ainult juhuslikke markForCheck () ridu ja mitmesuguste üksikute teenuste süsteeme, vaid ka me ei tea enam isegi, milline komponent sõltub millest ja milliseid andmeid kus see muudab .

Jõuame punktini, kus meie rakendusel on sadu komponente ja kümneid erinevaid singletoniteenuseid, kõik sõltuvad üksteisest. Mõistame, et me ei tea enam, mis sellest sõltub. Oleme lõpuks lahendamatu labürindiga esiotsa koodist.

Projekt on nii ülespuhutud otsese ja kaudse sõltuvusega meie komponentide vahel, et me eksime , ärritame , kuni lõpuks loobume ja vahetame lihtsalt tagasi taustaprogrammide / jQuery / 'kodeerimise juurde anything kõigele, mis oli 2018. aastal hõlpsam kui frontend. "jälle ...

‍ Nii see ei peaks minema.

Ehkki on tõsi, et raamistik peaks aitama meil oma rakendust hõlpsasti mastaapsena üles kirjutada, on tegelik tõde, et raamistik oli alati tähendanud lihtsalt meie koodi põhilisi tellinguid. Me ei saa kunagi loota ainult raamistikule, et hoida oma kood kontrolli all. Peame seda ise tegema.

Lahendus

Mõelgem veel kord sellele nutikale ja nukrale komponentide jaotusele. Rakenduses eksime mitte sellepärast, et see muster oleks vale, vaid seetõttu, et sageli teeme seda ebatäiuslikult.

Minu insenerikava selle mustri määratlemiseks on järgmine:

  1. Jagage komponendid nutikateks ja nukrateks.
  2. Hoidke komponente nii rumalaid kui võimalik.
  3. Otsustage, millal peaks komponent olema Loll asemel nutikas.

Kirjeldame neid punkte ükshaaval.

P.S. Kui eelistate slaidide sirvimist kui ajaveebi postitust, saate vaadata minu esitluse slaidid, mille ma andsin ng-poznan meetmel sama pealkirja all.

1. Jagage komponendid nutikateks ja nukrateks

Esiteks määratlegem, mis nutikad ja lollid komponendid tegelikult on.

  • Loll komponent on komponent, mis töötab nagu puhas funktsioon.
    (Puhas funktsioon on funktsioon, mis antud funktsiooni argumentide korral annab alati sama tagasiväärtuse.)
    Loll komponent on just selline. See on komponent, mis vastuvõetud andmete (sisendite) puhul näeb välja ja käitub alati samamoodi, võimaldades ka muid andmeid (sündmusi, väljundite kaudu).
Loll komponent
  • Nutikas komponent on komponent, mis sarnaneb pigem ebapuhta funktsiooniga.
    (Ebapuhas funktsioon on funktsioon, mis puutub kokku välismaailmaga: kas väliste teenistustelt andmete hankimisega või kõrvaltoimete tekitamisega.)
    Nutikas komponent on just selline. See ei sõltu ainult sisenditest, vaid ka mingist välisest andmest (välismaailm), mida ei edastata otse @Input () kaudu. See võib põhjustada ka mõnda kõrvaltoimet, mida ei väljastata liidese @Output () kaudu.
    Näiteks komponent, mis saab praeguseid kasutajaandmeid mujal vahendatud üksikute teenuste hulgast; välisest API-st; või LocalStorage'ist. Komponent, mis muudab välise teenuse olekut; väljastab API-kõne; või muudab salvestatud andmeid LocalStorage'is.
Nutikas komponent

Lollakat komponenti nimetatakse mõnikord ka “puhtaks”, “esitluslikuks”. Nutikat komponenti nimetatakse mõnikord “ebapuhtaks”, “ühendatud” ja “mahutiks”. Internetis ilmuvad erinevad määratlused, sõltuvalt neid kirjeldavast autorist või raamistikust, millele ta on keskendunud, kuid kogu kontseptsioon nende niiviisi sukeldumisest on tavaliselt sarnane.

Pidage meeles: nutikad vs loll pole riiklikud vs kodakondsuseta!
Inimesed veavad neid termineid sageli, kuid minu jaoks on nad üksteisega täiesti sõltumatud. Vaata:

  • rumalal komponendil pole väliseid sõltuvusi ja see ei põhjusta kõrvaltoimeid (kuid võib-olla ei oma kohalikku riiki).
  • nutikal komponendil on välised sõltuvused või see põhjustab kõrvaltoimeid (kuid võib-olla ei pruugi kohalik riik olla).
  • kodakondsuseta komponendil pole kohalikku olekut (kuid see võib siiski põhjustada kõrvaltoimeid).
  • Riiklikul komponendil on kohalik riik (kuid tal pole vaja mingeid sõltuvusi ega kõrvaltoimeid).

Panin joonise, et illustreerida seda selgemalt, kuidas neid kahte jaotust ühendada:

Nutikas / loll x oleklik / kodakondsuseta maatriks

Huvitav on see, et ainult nende jooniste põhjal saame juba tõsiseid järeldusi:

  • Loll-kodakondsuseta komponendi käitumist on kõige lihtsam ennustada ja mõista.
  • Loll-Stateful komponent näib samuti olevat lihtne, sest kuigi sellel on mõni kohalik riik, on see teistele läbipaistev.
    Teiste vaatevinklist võtab kõik see komponent vaid sisendite vastuvõtmist ja väljundite väljastamist.
  • Nutikaid komponente on kõige keerulisem haarata, kuna lisaks sisenditele ja väljunditele on need ka kuidagi välismaailmaga ühendatud, saades sealt andmeid ja / või põhjustades kõrvaltoimeid.

Nüüd on minu kogu selle ajaveebi postituse põhipunkt järgmine: nutikad komponendid on kõige kurjemad. Sest lisaks selgelt määratletud sisenditele ja väljunditele vajavad nad ka mingisuguseid väliseid sõltuvusi ja tekitavad mingisuguseid kõrvaltoimeid. Kahjuks on selliseid asju programmeerimisel alati raske kontrollida.

2. Hoidke komponente nii rumalana kui võimalik

Selle pärast ma ütlen, et peaksime enamiku oma komponentidest nukraks hoidma. Loll, ma mõtlen tõesti loll! Ma mõtlen, et hea rumal komponent:

  • ei tohiks sõltuda välistest teenustest - kui selle toimimiseks on vaja mõnda teavet, tuleks see sisestada @Input () kaudu;
  • ei tohiks põhjustada mingeid kõrvalmõjusid - kui see peaks midagi kiirgama, tuleks see väljastada hoopis @Output () abil;
  • ei tohiks oma sisendeid muteerida - sest kui see juhtub, põhjustab see tegelikult kõrvalmõju, mis põhjustab muutuse emakomponendi andmetes.
    Laps ei tohiks kunagi vanema andmeid otse redigeerida. Kui lapsevanemat on vaja teavitada sellest, et midagi on muutunud, peaks ta sellest sündmusena teatama, mille vanem peaks üles võtma ja siis vastavalt tegutsema.

Koodinäide:

3. Otsustage, millal peaks komponent olema Loll asemel nutikas

See punkt on raske. See pani mind kogu postitust paar korda ümber kirjutama;) Põhjus on selles, et selge eristamine, mis peaks olema nutikas ja mis mitte, ei ole nii lihtne. Kuid kuna ma vihkan seda, et arvutiteaduse otsused jäetakse arendaja “isikliku maitse” otsustada, jõudsin lõpuks nelja järelduseni:

3a. Kui see võib olla loll, siis tee sellest loll

Mõelge, milline on komponendi roll.

Kas rolli on paberil lihtne ennustada ja kirjeldada? Kui jah, tähendab see, et me ei pea teda nutikaks tegema. Sisenditel ja väljunditel peaks olema lihtne tema käitumist kontrollida.

Näited:

  • vormikontrolli komponent, mis võtab vastu praeguse väärtuse ja väljastab uusi väärtusi,
  • vormikomponent, mis võtab vastu algse vormi väärtuse ja väljastab uued vormi väärtused.

Põhimõtteliselt, kui pole põhjust mitte muuta komponenti lolliks, siis muutke see lihtsalt lolliks.

3b. Kui mitu last on võrdselt nutikad, tehke neist loll

Näiteks kui teil on otsinguleht koos mitme erineva filtriga, mis ühendavad otsingulehe olekut, kuid neil kõigil on sama roll - näidata praeguse filtri väärtust ja võimalusel seda muuta -, siis miks korrata sama nutikat loogikat kõigis nendest?

Selle asemel võiks meil olla lihtsalt nutikas FiltersListComponent, mis loob ühenduse otsingulehe olekuga ja edastab filtriväärtused selle all olevatele Lollide filtrikomponentidele.

Kümne nutika komponendi asemel on meil üks nutikas ja kümme lollit.

3c. Mis ei saa olla loll, tehke sellest nutikaks

Lõpuks jõuame punktini, kus me ei saa kõiki oma komponente tuimaks hoida. Vähemalt üks neist peab olema nutikas. Peame oma rakenduse olekut kuskil hoidma; peame API-d kuskilt kutsuma, eks?

Enamikul juhtudel on parim valik panna see ülemise vaate komponentidesse.

Pange tähele, et see ei tähenda, et peame loogika otse komponendi koodi sisestama. See võib olla eraldi teenuses, näiteks SearchPageControllerService. Või võib see olla Reduxi toimingutes, olekus ja reduktoris, kui me kasutame redux-tüüpi struktuuri.

Oluline on vaid see, et see nutikas komponent on tegelikult ainus, kellel on juurdepääs sellele välisele sõltuvusele, ja see on ainus, mis sellele sündmusi edastab.

Kõik selle nutika komponendi lapsed on lollid ja reageerivad maailmale ainult oma sisendite ja väljunditega.

Nii on lihtsam otsustada, kes teie arvates mida ja millal muudab ning mis sellest sõltub.

Näiteks peaks tüüpilisel otsingulehel nutikas olema ainult SearchPageComponent. Kõik muud tema vaates olevad komponendid, näiteks SearchPageResultsComponent, SearchPageFiltersComponent, SearchPageResultsItemComponent ja nii edasi, peaksid olema tuhmid.

Otsingulehe näide

3d. Kui nutikas muutub liiga suureks, jagage see eraldi Smartsideks.

„Muutke ülavaate komponent nutikaks” on väga hea reegel, kuid mõnes vaates ei pruugi see olla piisav.

Näiteks Gmaili peamisel postkasti lehel on järgmised funktsioonid:

  • loetleda ja hallata hiljutisi e-posti niite
  • loetleda ja hallata saadaolevaid kaustu
  • kuvage Hangouti võrgus olevaid inimesi
  • luba kiiresti uus kiri kirjutada

Kui meil oleks Gmaili lehel ainult üks nutikas komponent, peaks see võtma üle kõik oma kohustused. Üsna palju. On väga suur võimalus, et see on niikuinii tohutu tükk väga keerulist koodi.

Selle asemel võiksime selle jagada mõneks nutikaks komponendiks, millel on oma kohustused:

  • ThreadsListView
  • FoldersListView
  • HangoutPeopleListView
  • NewMessageModalView

Kõigi nende vaadete all oleks ainult tuimad lapsed. Niisiis, see on ikkagi juhitav.

Korduma kippuvad küsimused

1. Kuidas ja millal lubatakse mul sisestada välisteenuseid / sõltuvusi? Kas ma saan neist sõltuda ja kuidas ma saan nendega suhelda?

Üldiselt ei tohi kunagi välismaailmaga suhelda, kui te pole just nutikas komponent.

Nutika komponendina olete API-liideste ja rakenduse äriloogika ning teiste lollide komponentide vaheline mees.

Ainult nutikas komponent peaks suutma suhelda väliste API-dega, kutsudes üles nende funktsioonid ja tellides nende tagasiväärtused / lubadused / vaatlused.

Kui API-st tuleb midagi uut, värskendate nutika komponendi olekut, mis põhjustab muudatuse levimist selle lollideks.

Sama kehtib ka teise suunaga. Alati, kui teie tobe laps soovib midagi muuta, nt. ta soovib otsingutulemusi värskendada, see kiirgab ainult sündmust. Nutikas komponent võtab selle üles, kutsub uuesti välise API-liidese ja alles seejärel levitab muudatust oma tuimadele lastele, värskendades nende sisendeid.

Praktikas tähendab see, et ainult teie nutikad komponendid, mis asuvad teie komponendi puu ülaosas, suhtlevad väliste teenustega. Kõik ülejäänud, mis asuvad sügaval puus, on lõpuks lollid ja suhtlevad teistega ainult sisendite ja väljundite kaudu.

Siiski võib siiski esineda juhtumeid, kus tasub ühendada oma rumal komponent otse välismaailmaga. Näiteks:

  • Kui klõpsate rakenduses ProductItemComponent nuppu, võite rakenduse kohe suunata teisele URL-ile, ilma et peaksite väljastama ja püüdma sündmust @Output () productItemClick.
  • DateTimePickerComponent renderdades võite praeguse kasutaja ajavööndi hankida otse mõnest globaalsest muutujast.
  • Võimalike kasutajate loendis oma rakenduses UserSelectComponent võidakse hankida kõigi veebikasutajate loend otse teie rakenduse globaalselt saadavast API-teenusest.

Need kolm ülaltoodud näidet on minu jaoks okei, sest isegi kui neil on seos välismaailmaga, on see vaid globaalselt kättesaadavate andmete saamine ja nende andmete muutmine teie rakenduses harva. Kuid pidage meeles, et see rikub endiselt reegleid. Kui tekib olukord, kus soovime, et rakenduses ProductItemComponent # productItemClick käituksime teisiti, tahame kasutada kuupäevaTimePickerComponent konkreetse esinemisjuhu jaoks teistsugust ajavööndit või kasutada UserSelectComponenti teistsugust kasutajate valikut, siis peaksime IMO loomuliku loomulikkuse juurde tagasi jääma. @Input () ja @Output () liidese kasutamine.

2. Kuidas rakendada muudatust oma vanema suhtes? Põhimõtteliselt kuidas ma saan oma rakendusega midagi muuta?

Te väljastate sündmuse ja lapsevanem püüab selle kinni ning tegeleb muudatusega ettevõttesiseselt. Tegelikult moodustavad komponentide sisendid ja väljundid teatud tüüpi tsükli:

  1. Laps B kiirgab sündmust @Output ();
  2. Vanem A püüab sündmuse @Output () kätte ja haldab seda kas:
    a) sündmuse edastamine ülespoole - teise @Output () või
    b) muudatuse sisemine käsitlemine - värskendades selle kohalikku olekut (ja valikuliselt käsikäes nurgal, et ta kontrolliks enda ja laste seoseid ChangeDetectorRef # atklātChanges () abil);
  3. Laps B saab uusi andmeid saidi @Input () kaudu, selle ngOnChanges () tagasihelistamisele helistatakse ning selle vaadet ja laste köiteid kontrollib Nurk automaatselt.

P.S. Lisateavet nurkade muutuste tuvastamise mehhanismi kohta leiate minu eelmisest artiklist: Nurga toimimise valdamine, 1. osa - muutuste detektori võlu kukutades.)

3. Kas ma saan oma sisendeid kunagi muuta?

Ei. Sisendi muutmine on puhta funktsiooni rikkumine - sellel on tegelikult kõrvalmõju, mille tõttu teie vanema komponendi andmed muutuvad, ilma et keegi seda teeks.

Kui soovite oma vanema komponendi muutmist, peaksite selle asemel väljastama sündmuse @Output () abil ja hakkama hakkama vanema komponendi muutmisega.

4. Ma pean oma CityListItemComponenti leidma praeguse linna ilma. Kas ma saan sellesse oma pisikese WeatherServicei lihtsalt süstida ja kasutada seda otse nagu see.cityWeather = WeatherService.getWeatherForCity (this.city)?

Ei, ma arvan, et pigem ei peaks. CityListItemComponent kõlab nagu komponent, mis on sügavalt sisse pestud ja mis peaks olema Loll. Kui teete selle sõltuvaks välisest teenusest nagu WeatherService, pole see enam loll. Selle käitumist on keerulisem ennustada, kuna ebapuhtat funktsiooni on alati raskem mõista kui puhast.

Selle asemel võite näiteks saada kõigi linnade praeguse ilma selle komponendi kohta, mis tegelikult linnad saab, ja seejärel edastada see @Input () abil allapoole.

5. Ma pean värskendama antud linna praegust ilma, kasutades värskendamisnuppu, mis asub minu CityListItemComponentis. Kas ma saan oma pisikese WeatherService'i lihtsalt sinna süstida ja otse WeatherService.refreshWeatherForCity (this.city) helistada?

Ei, ma arvan, et pigem ei peaks. Kui CityListItemComponent on loll komponent, ei tohiks see põhjustada mingeid kõrvaltoimeid.

Selle asemel eraldage @Output () weatherRefresh sündmus ja käsitlege seda nutikas esivanemas, mis tegelikult kogu nimekirja ilmateadet haldab.
Selle põhjuseks on asjaolu, et soovite oma komponentide koodi lihtsana hoida. Loll CityListItemComponent peaks hoolitsema ainult linna üksikasjade kuvamise ja kasutajaliidese sündmuste väljastamise eest. Sellel ei tohiks olla mingis API-s loogikat praeguse linna ilma hankimiseks.

Kogu see eraldamine seisneb selles, et teie komponendid oleksid tahked. Linnade nimekirja lehel ilmade ilmastiku saamiseks ja värskendamiseks peaks olema ainult ühes kohas. Eeldatavasti loendi või loendi lehe kood (või selle redutseeritud olek ja toimingud).

Nutika / lolli jagamise plussid ja miinused

Plussid

  1. Saate hõlpsalt ennustada lollaka komponendi käitumist.
    See on sama lihtne kui selle avalike sisendite ja väljundite liides.
    (Mis muuseas koos TypeScripti tüüpdefidega on teie koodi vinge dokumentatsioon.)
  2. Lollaka komponendi käitumist saate hõlpsalt testida.
    Lollaka komponendi testimine on nii lihtne kui:
    1. Määrake sisendväärtused
    2. Kohene komponent
    3. Käitu komponendi suhtes (st klõpsa sellel)
    4. Väida, et konkreetne „@Output ()” on väljastatud.
    Nutika komponendi testimine nõuab tavaliselt palju enamat: väliste sõltuvuste torkamist, kõrvaltoimete kontrollimist jne.
  3. Lollaka komponendi käitumist saate (üsna) hõlpsalt muuta ilma asju katkestamata.
    Alati, kui muudate tobedat komponenti:
    - Veenduge, et vana liides töötab endiselt (või otsige ja asendage selle komponendi vanad kasutusviisid, mida saate tänu TypeScriptile hõlpsasti teha)
    - Komponendi peamine käitumine töötab endiselt nagu ette nähtud.
    Te ei pea kontrollima, kas välised sõltuvused seda komponenti rikuvad või kui see tekitab varasemast muid kõrvaltoimeid. Seda pole kunagi tehtud ja ei juhtu ka kunagi.
  4. Teie rakenduse peamist loogikat kontrollivad ainult teie nutikad komponendid.
    Enam ei pea te lugema kogu oma koodide hoidlat, vaid lihtsalt selleks, et näha, kes mida tõmbab ja mida muudetakse, mida ja kus.
    Nüüd näete enamikku sellest enamasti nutikate komponentide HTML-malli vaadates.
  5. See on rohkem esinev.
    Kuna nüüd teate, millest täpselt sõltub, ei vaja te enam NgZone'i ega maagilist muudatuste tuvastamise mehhanismi, mis kontrollib kõikjal toimuvat. Võite lihtsalt NgZone vahele jätta ja kasutada ChangeDetectionStrategy.OnPush kõiki oma lolle komponente.
  6. See aitab teil vigu vältida.
    Teie koodi vähem sidumist;
    Jaotades selle väiksemateks, tahkemateks ja puhasteks bittideks;
    Kõrvaltoimete vältimine;
    Tippitud sisendite ja väljundite kasutamine andmete ülekandmiseks enamikus teie rakenduses;
    - kõik see vähendab teie koodi keerukust ja vähendab samal ajal võimalust, et teie koodis ilmnevad vead.
  7. Boonus: saate vahele jätta, kasutades kokku ChangeDetectorRef # markForCheck ().
    Seda pole lihtsalt vaja kasutada.
    Me ei pea nurka teavitama, et vanemkomponendis on midagi muutunud, kuna kuna see on alamkomponent, ei muuda me seda enam otseselt.
    (Kui me seda teeme, siis teeme seda läbi "@Output ()" ja vanem tegeleb muudatusega ise.)

Miinused

  1. Te ei saa sõltuvusi süstida kuhu iganes soovite.
    Peate alati ette mõtlema, milliseid andmeid teie komponendid vajavad ja kuidas neid neile edastatakse.
  2. Sisend / väljund kaudu edastatud andmeid ei saa muteerida.
    Selle asemel kasutate kohalikku riiki või jätate andmete hooldamise nutikate vanemate hooleks.

TL; DR:

  • Jagage oma komponendid selgelt nutikateks ja nukrateks komponentideks.
  • Rakendage oma rumalad komponendid hästi.
    Ärge kunagi mutage @Input () väärtusi.
    Vältige sõltuvust välistest teenustest - kasutage selle asemel @Input ().
    Vältige kõrvaltoimete tekitamist - kasutage selle asemel @Output ().
  • Vältige koodis häkiliste otseteede kasutamist. Pikas perspektiivis sunnivad need teid tegelikult oma koodile rohkem aega kulutama ja pole üldse otseteed. Need muudavad kogu rakenduse mõistmise, muutmise ja laiendamise keerukamaks.

Hakkasime kõiki neid reegleid rakendama paar kuud tagasi oma tohutul värbamisprogrammil Nurga all üks leht ja näeme juba selle tohutuid eeliseid.

Mida arvate kõigist neist reeglitest? Kas rakendate oma meeskondades ja projektides sarnaseid? Võib-olla pole sa millegi suhtes nõus? Andke meile kommentaarides teada.

Vaadake julgelt ka minu ettekannet, mille hiljuti tegin samal teemal. Võib-olla töötavad slaidid teie jaoks paremini kui ajaveebi postitus.

-

P.S. Värbaja värbamine! Kui otsite tööd Angular + TypeScripti või Elixir + Phoenix Framework taustal, eelistatavalt Poolas Poznańis, vaadake meie karjäärivõimalusi. Me võime teile ideaalselt sobida.