Zrozumienie publikowania / subskrypcji Meteor


84

Mam prostą aplikację, która wyświetla listę plików Projects. Usunąłem autopublishpaczkę, więc nie wysyłam wszystkiego do klienta.

 <template name="projectsIndex">    
   {{#each projects}}      
     {{name}}
   {{/each}}
 </template>

Gdy autopublishzostało włączone, wyświetli się wszystkie projekty:

if Meteor.isClient
  Template.projectsIndex.projects = Projects.find()

Po usunięciu muszę dodatkowo wykonać:

 if Meteor.isServer
   Meteor.publish "projects", ->
     Projects.find()
 if Meteor.isClient
   Meteor.subscribe "projects"
   Template.projectsIndex.projects = Projects.find()

Czy zatem słuszne jest stwierdzenie, że find()metoda po stronie klienta przeszukuje tylko rekordy, które zostały opublikowane po stronie serwera? Drażniło mnie to, bo czułem, że powinienem zadzwonić tylko find()raz.

Odpowiedzi:


286

Kolekcje, publikacje i subskrypcje to trudny obszar Meteor, który dokumentacja może omówić bardziej szczegółowo, aby uniknąć częstego zamieszania , które czasami jest wzmacniane przez mylącą terminologię .

Oto Sacha Greif (współautor DiscoverMeteor ) wyjaśniający publikacje i subskrypcje na jednym slajdzie:

subskrypcje

Aby właściwie zrozumieć, dlaczego musisz dzwonić find()więcej niż raz, musisz zrozumieć, jak działają kolekcje, publikacje i subskrypcje w Meteor:

  1. Kolekcje definiujesz w MongoDB. Żaden Meteor nie był jeszcze zaangażowany. Te kolekcje zawierają rekordy bazy danych (nazywane również „dokumentami” zarówno przez Mongo, jak i Meteor , ale „dokument” jest bardziej ogólny niż rekord bazy danych; na przykład specyfikacja aktualizacji lub selektor zapytania to również dokumenty - obiekty JavaScript zawierające field: valuepary).

  2. Następnie definiujesz kolekcje na serwerze Meteor za pomocą

    MyCollection = new Mongo.Collection('collection-name-in-mongo')
    

    Te kolekcje zawierają wszystkie dane z kolekcji MongoDB i można MyCollection.find({...})na nich uruchomić , co zwróci kursor (zestaw rekordów z metodami do ich iteracji i zwrócenia).

  3. Kursor ten jest (przez większość czasu) używany do publikowania (wysyłania) zestawu rekordów (zwanego „zestawem rekordów” ). Opcjonalnie możesz opublikować tylko niektóre pola z tych rekordów. To zestawy rekordów ( nie kolekcje) subskrybują klienci . Publikowanie odbywa się za pomocą funkcji publikowania , która jest wywoływana za każdym razem, gdy subskrybuje nowy klient, i która może przyjmować parametry do zarządzania, które rekordy mają zwrócić (np. Identyfikator użytkownika, aby zwrócić tylko dokumenty tego użytkownika).

  4. Na kliencie masz kolekcje Minimongo, które częściowo odzwierciedlają niektóre rekordy z serwera. „Częściowo”, ponieważ mogą zawierać tylko niektóre pola i „niektóre rekordy”, ponieważ zazwyczaj chcesz wysłać klientowi tylko te rekordy, których potrzebuje, aby przyspieszyć ładowanie strony, i tylko te, których potrzebuje i do których ma uprawnienia dostęp.

    Minimongo jest zasadniczo nietrwałą implementacją Mongo w pamięci, w czystym języku JavaScript. Służy jako lokalna pamięć podręczna, która przechowuje tylko podzbiór bazy danych, z którą pracuje ten klient. Zapytania na kliencie (find) są obsługiwane bezpośrednio z tej pamięci podręcznej, bez komunikacji z serwerem.

    Te kolekcje Minimongo są początkowo puste. Są wypełnione

    Meteor.subscribe('record-set-name')
    

    wezwania. Zauważ, że parametr subskrypcji nie jest nazwą kolekcji; jest to nazwa zestawu rekordów , którego serwer użył w publishwywołaniu. subscribe()Wezwanie subskrybuje klienta do zestawu rekord - podzbiór rekordów z kolekcji serwera (np najnowsze posty na blogach 100), przy czym wszystkie lub podzbiór pól w każdym rekordzie (na przykład tylko titlei date). Skąd Minimongo wie, w której kolekcji umieścić przychodzące rekordy? Nazwa kolekcji będzie collectionargumentem w Publish przewodnika added, changedi removedwywołania zwrotne, lub jeśli takowych brak (co ma miejsce w większości przypadków), będzie to nazwa kolekcji MongoDB na serwerze.

Modyfikowanie rekordów

W tym miejscu Meteor sprawia, że ​​jest to bardzo wygodne: kiedy zmodyfikujesz rekord (dokument) w kolekcji Minimongo na kliencie, Meteor natychmiast zaktualizuje wszystkie szablony, które od niego zależą, a także wyśle ​​zmiany z powrotem na serwer, który z kolei zapisze zmiany w MongoDB i wyśle ​​je do odpowiednich klientów, którzy subskrybowali zestaw rekordów zawierający ten dokument. Nazywa się to kompensacją latencji i jest jedną z siedmiu podstawowych zasad Meteor .

Wiele subskrypcji

Możesz mieć kilka subskrypcji, które pobierają różne rekordy, ale wszystkie trafią do tej samej kolekcji na kliencie, jeśli pochodzą z tej samej kolekcji na serwerze, na podstawie ich _id. Nie jest to jasno wyjaśnione, ale sugerowane przez dokumentację Meteor:

Kiedy subskrybujesz zestaw rekordów, mówi on serwerowi, aby wysłał rekordy do klienta. W sklepach klienckie te zapisy w miejscowych zbiorów Minimongo, o tej samej nazwie co collectionargumentu użytego w publikowania przewodnika added, changedi removedzwrotnych. Meteor będzie kolejkował przychodzące atrybuty, dopóki nie zadeklarujesz Mongo.Collection na kliencie z pasującą nazwą kolekcji.

Co nie wyjaśnił, co się dzieje, kiedy nie jawnie użyć added, changedi removedczy w ogóle publikować teleskopowe - co jest przez większość czasu. W tym najczęstszym przypadku argument kolekcji jest (co nie dziwi) pobierany z nazwy kolekcji MongoDB, którą zadeklarowałeś na serwerze w kroku 1. Ale oznacza to, że możesz mieć różne publikacje i subskrypcje o różnych nazwach, a wszystkie rekordy trafią do tej samej kolekcji na kliencie. Aż do poziomu pól najwyższego poziomu , Meteor dba o wykonanie określonej unii między dokumentami, tak aby subskrypcje mogły się nakładać - publikuj funkcje, które wysyłają różne pola najwyższego poziomu do klienta, który działa obok siebie i na kliencie, dokument w kolekcja będziepołączenie dwóch zestawów pól .

Przykład: wiele subskrypcji wypełniających tę samą kolekcję na kliencie

Masz kolekcję BlogPosts, którą deklarujesz w ten sam sposób zarówno na serwerze, jak i kliencie, mimo że robi to różne rzeczy:

BlogPosts = new Mongo.Collection('posts');

Na kliencie BlogPostsmożna pobrać rekordy z:

  1. subskrypcja ostatnich 10 postów na blogu

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. subskrypcja postów aktualnego użytkownika

    // server
    Meteor.publish('posts-current-user', function publishFunction() {
      return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
      // this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
    }
    Meteor.publish('posts-by-user', function publishFunction(who) {
      return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
    }
    
    // client
    Meteor.subscribe('posts-current-user');
    Meteor.subscribe('posts-by-user', someUser);
    
  3. subskrypcja najpopularniejszych postów

  4. itp.

Wszystkie te dokumenty pochodzą z postskolekcji w MongoDB, za pośrednictwem BlogPostskolekcji na serwerze i trafiają do BlogPostskolekcji na kliencie.

Teraz możemy zrozumieć, dlaczego trzeba dzwonić find()więcej niż jeden raz - za drugim razem u klienta, ponieważ dokumenty ze wszystkich abonamentów trafią do tej samej kolekcji i trzeba będzie pobierać tylko te, na których nam zależy. Na przykład, aby uzyskać najnowsze posty na kliencie, po prostu odzwierciedlasz zapytanie z serwera:

var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});

Spowoduje to zwrócenie kursora do wszystkich dokumentów / rekordów, które klient otrzymał do tej pory, zarówno najlepszych postów, jak i postów użytkownika. ( dzięki Geoffrey ).


10
To jest świetne. Może warto wspomnieć, co się stanie, jeśli zrobisz BlogPosts.find({})na kliencie po zasubskrybowaniu obu publikacji - tj. Zwróci to kursor wszystkich dokumentów / rekordów znajdujących się obecnie na kliencie, zarówno najlepszych postów, jak i postów użytkownika. Widziałem inne pytania na SO, w których pytający był zdezorientowany.
— Geoffrey Booth

3
To jest świetne. dzięki. Ponadto kolekcja Meteor.users () jest nieco zagmatwana, ponieważ jest automatycznie publikowana po stronie klienta. Czy można dodać trochę do powyższej odpowiedzi, aby określić kolekcję users ()?
— Jimmy MG Lim

3
Nawet jeśli o wiele więcej niż pierwotnie pytano, myślę, że @DVG powinno oznaczyć ten wspaniały zapis jako zaakceptowaną odpowiedź. Dzięki Dan.
— physiocoder

1
Dzięki @DanDascalescu, Świetne wyjaśnienie, które wiele dla mnie wyjaśniło, jedyna rzecz, którą śledząc dokumenty meteor o „kolekcjach” po przeczytaniu twojego wyjaśnienia, myślę, że BlogPostsnie jest to kolekcja, to zwrócony obiekt, który ma metody takie jak „wstaw”, „aktualizacja "..etc, a prawdziwa kolekcja jest również postsw kliencie i serwerze.
— UXE

4
Czy można wzywać tylko zestaw rekordów, który subskrybujesz? Tak jak w przypadku, czy możliwe jest bezpośrednie pobranie rekordu ustawionego w moim javascript zamiast lokalnego odpytywania bazy danych Minimongo?
— Jimmy Knoot

27

Tak, funkcja find () po stronie klienta zwraca tylko dokumenty znajdujące się na kliencie w Minimongo. Z dokumentów :

Na kliencie tworzona jest instancja Minimongo. Minimongo jest zasadniczo nietrwałą implementacją Mongo w pamięci, w czystym języku JavaScript. Służy jako lokalna pamięć podręczna, która przechowuje tylko podzbiór bazy danych, z którą pracuje ten klient. Zapytania na kliencie (find) są obsługiwane bezpośrednio z tej pamięci podręcznej, bez komunikacji z serwerem.

Jak powiedziałeś, publikuj () określa, jakie dokumenty będzie miał klient.


1

Podstawowa zasada kciuka jest tutaj publishi subscribednazwy zmiennych powinny być takie same na klienta i po stronie serwera.

Nazwy kolekcji po stronie Mongo DB i po stronie klienta powinny być takie same.

Załóżmy, że używam opcji publikowania i subskrybowania mojej kolekcji o nazwie employees, jak będzie wyglądał kod


po stronie serwera

Tutaj użycie varsłowa kluczowego jest opcjonalne (użyj tego słowa kluczowego, aby ustawić kolekcję lokalną dla tego pliku).

CollectionNameOnServerSide = new Mongo.Collection('employees');   

Meteor.publish('employeesPubSub', function() { 
    return CollectionNameOnServerSide.find({});     
});

plik js po stronie klienta

CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');

Template.templateName.helpers({
  'subcribedDataNotAvailable' : function(){
        return !employeesData.ready();
    },
   'employeeNumbers' : () =>{
       CollectionNameOnClientSide.find({'empId':1});
  }
});

plik .html po stronie klienta

Tutaj możemy użyć subcribedDataNotAvailablemetody pomocniczej, aby wiedzieć, czy dane są gotowe po stronie klienta, jeśli dane są gotowe, a następnie wydrukować numery pracowników metodą employeeNumberspomocniczą.

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>

0
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.