jQuery vs raamistikud ehk rakendusarhitektuuri kadunud kunst

Üks põhjus, miks inimesed valivad JavaScripti raamistikud lihtsamate raamatukogude (nt jQuery) asemel, on see, et raamistikud aitavad teil koodi struktureerida ja et jQuery põhjustab hävitamatuid jamasid.

Lugesin kasutajaliidese võrdlust, mis näitab seda eeldust hästi, rakendades lihtsat märkmete tegemise rakendust mitme kasutajaliidese tehnoloogiaga, sealhulgas jQuery abil. JQuery kood oli tõepoolest jama ja mitmesugused raamistikud andsid palju puhtama koodi. Kuid kas see peab nii olema?

Rakendusarhitektuuri kadunud kunst

jQuery on lihtsalt teek, see ei anna teile juhiseid rakenduse struktureerimise kohta. See jätab teile rakendusearhitektuuri väljatöötamise. Kas saaksime seda probleemi vältida, järgides eelpool mainitud võrdluse näidet:

Siit hakkame nägema mõningaid raskusi jQuery abil - kuigi DOM-i manipuleerimiskood ise on üsna sirgjooneline, muutub DOM-i erinevate osade meeldejätmine üha keerukamaks, mida tuleb iga muudatusega värskendada.

See ei ole jQuery probleem, vaid üldine programmeerimisprobleem. Kui olete olekut jaganud, siis kuidas vähendada kõigi andmete muutmise keerukust, mida tuleb iga muudatusega värskendada? OOP mõttes on vastus kapseldumine.

Kas suudame jQuery ja tavalise JavaScripti OOP-iga midagi arusaadavat välja pakkuda?

Selle demonstratsiooni jaoks laenan HTML-i ja CSS-i malli lingitud artiklist.

Siin on täielik näide OOP-stiilis koos kõigi DOM-i värskendustega, mis on kapseldatud ühte vastutusalasse. Väljakutse suurendamiseks rakendasin koodi ES5-s. ES6 ja Typescript on väga kasulikud ja meeldivad kasutamiseks (eriti Typescript), kuid need pole korraliku arhitektuuri jaoks vajalikud.

Olen seda lähenemisviisi kasutanud väikeste ja keskmise keerukusega saitide arendamisel ilma, et asjad käest läheksid.

Kas see tähendab, et peaksime loobuma raamistikest? Üldse mitte! Keerukamate rakenduste jaoks pakuvad raamistikud väga häid tööriistu andmevoo haldamiseks, muudatuste levitamiseks, sõltuvuse süstimiseks jne.

Kuid raamatukogude kasutamine ei tähenda spagetikoodi ja teie rakenduse korraldamiseks ei peaks te sõltuma raamistikest. Rakendusarhitektuuri kunsti ei tohiks alahinnata!

Siit saate teada, kuidas samm-sammult arhitektuuri arendasin.

1. samm: kuvage märkmete loend

Alustame märkme põhimääratlusega:

var Märkus = (funktsioon () {
  var id = 0;
  tagastamise funktsioon (keha) {
    this.id = id ++;
    see.keha = keha;
    this.ts = uus kuupäev ();
  }
}) ();

See trikk võimaldab meil kapseldada id-väärtuse, mida suurendatakse automaatselt iga meie loodud uue märkme korral, nii et igal märkmel on kordumatu ID.

See võimaldab meil luua lihtsa märkmete komplekti alustamiseks:

var märkused = [uus märkus ("märkus 1"), uus märkus ("märkus 2")];

Alustame järgmise põhistruktuuriga:

Klassi NotesApp kohustus on kooskõlastada teiste klassidega ja hallata märkmete loendit haldavat DOM-i vanema sõlme.

NoteSelectori klassi ülesandeks on DOM-i sõlmede haldamine pealkirja ja ajatempli jaoks.

var NotesApp = funktsioon (sõlm, märkmed) {
  // Eemaldage loendiüksuse element, et seda mallina kasutada
  this.noteSelectorTpl = node.find ('. märkmevalija'). eemalda ();
  // NotesApp haldab märkmete loendit ja põhikonteinerit
  this.noteSelectorContainer = node.find ('. märkmevalijad');
  this.noteSelectors = [];
  $ .each (märkmed, (funktsioon (i, märkus) {this.addNote (note);})
                .bind (see));
}
NotesApp.prototype.addNote = funktsioon (märkus) {
  // Kloonige loendiüksuse mall ja looge uus NoteSelector
  // näiteks selle haldamiseks
  this.noteSelectors.unshift (uus NoteSelector (
    see.noteSelectorTpl.clone (). prependTo (this.noteSelectorHolder),
    Märge));
}
var NoteSelector = funktsioon (sõlm) {
  this.node = sõlm;
  this.title = node.find ('. märkmevalija pealkiri');
  this.timestamp = node.find ('. märkmevalija-ajatempel');
  // Vaese mehe andmete sidumine
  see.title.tekst (see.note.title ());
  this.timestamp.text (this.note.timestamp ());
}
Note.prototype.title = function () {
  tagastage see.keha.pikkus <15? see.keha:
    see.keha.liis (0, 15) + '...';
}
Note.prototype.timestamp = function () {
  tagasta see.ts.toUTCString ();
}

Rakendus käivitatakse järgmiselt:

$ (function () {new NotesApp ($ ('# app'), notes)});

JavaScript kaardistab järgmise HTML-i struktuuri:

  
    
      

Esimene märkus ...

      

Ajatempel siin ...

    
  

2. samm: valige märkus

Märkme valimiseks peame lisama veel ühe kaasautori NoteEditor, mis haldab redaktori juhtelementide DOM-i olekut.

Pange tähele, et kõik kaastöötajad on omavahel tihedalt seotud. NoteSelector peab teadma nii NoteAppist kui NoteEditorist, et käsku NoteAppil värskendada valikut ja NoteEditoril värskendada selle sisu valitud märkmega. Kuid vähemalt DOM-i muudatused on piiratud ühe vastutusalaga ja me piirdume koostöö koordineerimisega kõrgel tasemel, nii et olukorda on parandatud.

Saame selle probleemiga tegeleda lahtisidumisega, tutvustades sündmusi vaadeldavates riikides, mis võimaldavad kaastöötajatel registreerida üldisi huvisid, nii et nad ei pea üksteist konkreetselt teadma. Seal on vaatlusalused ja reaktiivsed komponendid abiks arhitektuuri täiustamisel. Lihtsuse huvides kinnitan demonstreerimise eesmärgil tihedama siduri, mis pole väikese arvu kaasautorite jaoks suur probleem.

var NotesApp = funktsioon (sõlm, märkmed) {
  ...
  // Lubage NoteEditoril hallata märkme redaktori juhtelemente
  this.editor = new NoteEditor (node.find ('. note-editor'));
}
NotesApp.prototype.setSelected = funktsioon (valik) {
  this.selectedNote = valik;
}
NotesApp.prototype.deactivateSelection = function () {
  $ .each (this.noteSelectors, function (i, sel) {
    sel.lülita ();
  });
}
var NoteSelector = funktsioon (sõlm, rakendus, toimetaja) {
  ...
  // Teiste suhtlusvormidega suhelge OOP-stiiliga, mitte läbi
  // DOM
  this.app = rakendus;
  this.editor = toimetaja;
  // Vaese mehe andmete sidumine
  see.title.tekst (see.note.title ());
  this.timestamp.text (this.note.timestamp ());
  this.node.click (this.select.bind (this));
}
NoteSelector.prototype.select = function () {
  // Öelge rakendusel valiku desaktiveerimiseks (ärge muretsege
  // kuidas DOM-i muudetakse)
  this.app.deactivateSelection ();
  // See on DOM-i muudatus, mis meid huvitab
  this.node.addClass ('aktiivne');
  // Suhtle valikumuutusega toimetajaga (ei muretse
  // DOM-i muutmise kohta)
  this.editor.editNote (käesolev märkus, see);
  this.app.setSelected (see);
}
NoteSelector.prototype.deactivate = function () {
  this.node.removeClass ('aktiivne');
}
var NoteEditor = funktsioon (sõlm) {
  this.timestamp = node.find ('. note-editor-info'). text ('');
  this.editor = node.find ('. note-editor-input'). text ('')
}
NoteEditor.prototype.editNote = funktsioon (märkus) {
  this.timestamp.text (note.timestamp ());
  this.editor.val (note.body);
}

3. samm: värskendage märkuse pealkirja dünaamiliselt, kui märkust on redigeeritud

var NoteEditor = funktsioon (sõlm) {
  ...
  this.editor.on ('sisend', this.onUpdate.bind (this));
}
NoteEditor.prototype.editNote = funktsioon (märkus, valija) {
  ...
  // Jälgige kaastöölisobjekte, mitte DOM-i sõlme
  this.note = märkus;
  see.valija = valija;
}
NoteEditor.prototype.onUpdate = function () {
  // vaese mehe kahesuunaline köitmine
  if (this.note) {
    this.note.body = this.editor.val ();
    // Teavitage valijat muudatustest (ei muretse
    // DOM-i muudatus)
    this.selector.refresh ();
  }
}
NoteSelector.prototype.refresh = function () {
  see.title.tekst (see.note.title ());
  this.timestamp.text (this.note.timestamp ());
}

4. samm: märkmete lisamine ja kustutamine

var NotesApp = funktsioon (sõlm, märkmed) {
  ...
  sõlm.find ('. märkus-uus'). klõpsake (see.onNewNote.bind (see));
  sõlm.find ('. note-delete') .klõpsake nuppu (this.onDeleteNote.bind (this));
}
NotesApp.prototype.onNewNote = function () {
  this.addNote (uus märkus ('uus märkus'));
}
NotesApp.prototype.onDeleteNote = function () {
  if (this.selectedNote) {
    this.selectedNote.remove ();
    this.noteSelectors = $ .grep (
      see.note Valijad,
      (funktsioon (märkus) {
        tagasi! this.selectedNote.equals (märkus);
      }). seo (see));
    this.resetSelection ();
  }
}
NotesApp.prototype.resetSelection = function () {
  this.selectedNote = this.noteSelectors && this.noteSelectors [0];
  if (this.selectedNote) {
    this.selectedNote.activate ();
  }
}
NoteSelector.prototype.equals = funktsioon (see) {
  tagastage see.note.id === see.note.id;
}
NoteSelector.prototype.remove = function () {
  this.node.remove ();
}

5. samm: märkmete otsimine

var NotesApp = funktsioon (sõlm, märkmed) {
  ...
  this.search = node.find ('. note-search') on ('input',
      this.onSearch.bind (see));
}
NotesApp.prototype.onSearch = function () {
  // NotesApp annab igale NoteSelectorile teada, et otsingukriteeriumid on täidetud
  // muudetud (ei muretse sisu ja
  // DOM-sõlmede näitamine / peitmine. Hea OOP on läbimise kunst
  // kurat!)
  $ .each (this.noteSelectors, (funktsioon (i, märkus)) {
    note.showIfNotMatches (this.search.val ());
  }). seo (see));
}
NoteSelector.prototype.showIfNotMatches = funktsioon (kriteeriumid) {
  if (kriteeriumid && this.note.body.toLowerCase (). indexOf (
      kriteeriumid.toLowerCase ()) <0) {
    this.node.hide ();
  }
  veel {
    this.node.show ();
  }
}