Розуміння Meteor Публікація / Підписка


84

У мене налаштовано просту програму, яка показує список Projects. Я видалив autopublishпакет, щоб не надсилати все клієнту.

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

Коли його autopublishбуло ввімкнено, це відображало б усі проекти:

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

Після його видалення я повинен додатково зробити:

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

Отже, чи правильно говорити, що find()метод на стороні клієнта здійснює пошук лише записів, опублікованих на стороні сервера? Це мене спотикало, бо я відчував, що мені слід телефонувати лише find()один раз.

Відповіді:


286

Колекції, публікації та підписки - це хитра область Метеора, яку в документації можна обговорити більш докладно, щоб уникнути частих плутанин , які іноді посилюються заплутаною термінологією .

Ось Саша Грейф (співавтор DiscoverMeteor ) пояснює публікації та підписки на одному слайді:

передплати

Щоб правильно зрозуміти, чому вам потрібно зателефонувати find()більше одного разу, потрібно зрозуміти, як працюють колекції, публікації та підписки в Meteor:

  1. Ви визначаєте колекції в MongoDB. Метеор ще не задіяний. Ці колекції містять запис бази даних (також звані «документи» на обох Монго і Метеор , але «документ» є більш загальним , ніж записи бази даних, наприклад, специфікація поновлення або селектор запиту документи теж - JavaScript об'єктів , що містять field: valueпари).

  2. Потім ви визначаєте колекції на сервері Meteor за допомогою

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

    Ці колекції містять усі дані з колекцій MongoDB, і ви можете працювати MyCollection.find({...})на них, що поверне курсор (набір записів із методами їх перебору та повернення).

  3. Цей курсор (більшу частину часу) використовується для публікації (надсилання) набору записів (так званий "набір записів" ). За бажанням ви можете опублікувати лише деякі поля з цих записів. Клієнти передплачують саме набори записів (а не колекції) . Публікація здійснюється за допомогою функції публікації , яка викликається кожного разу, коли новий клієнт підписується, і яка може приймати параметри, щоб керувати тим, які записи повертати (наприклад, ідентифікатор користувача, щоб повернути лише документи цього користувача).

  4. На клієнті у вас є колекції Minimongo, які частково відображають деякі записи із сервера. "Частково", оскільки вони можуть містити лише деякі поля, а "деякі записи", оскільки зазвичай ви хочете надіслати клієнту лише ті записи, які йому потрібні, щоб пришвидшити завантаження сторінки, і лише ті, які йому потрібні та має дозвіл на доступ.

    Minimongo - це, по суті, непостійна реалізація Mongo в чистому JavaScript. Він служить локальним кешем, який зберігає лише підмножину бази даних, з якою працює цей клієнт. Запити на клієнті (find) подаються безпосередньо з цього кешу, не спілкуючись із сервером.

    Ці колекції Minimongo спочатку порожні. Вони заповнені

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

    дзвінки. Зверніть увагу, що параметр для підписки не є назвою колекції; це ім'я набору записів, який сервер використовував у publishдзвінку. subscribe()Виклик виписує клієнт до набору записів - підмножина записів з колекції сервера (наприклад , останні 100 повідомлень в блозі), з усіма або підмножиною полів в кожному записі (наприклад , тільки titleі date). Звідки Minimongo знає, до якої колекції розмістити вхідні записи? Назва колекції буде collectionаргумент , який використовується в публікують обробника added, changedі removedзворотні виклики, або , якщо такі відсутні (що має місце в більшості випадків), то це буде назва колекції MongoDB на сервері.

Змінення записів

Тут Meteor робить речі дуже зручними: коли ви змінюєте запис (документ) у колекції Minimongo на клієнті, Meteor миттєво оновлює всі шаблони, які залежать від нього, а також відправляє зміни назад на сервер, який, у свою чергу збереже зміни в MongoDB і надішле їх відповідним клієнтам, які підписалися на набір записів, включаючи цей документ. Це називається компенсацією затримки і є одним із семи основних принципів Метеора .

Кілька підписок

Ви можете мати купу підписок, які збирають різні записи, але всі вони опиняться в одній колекції на клієнті, якщо надходять з тієї ж колекції на сервері, на основі їх _id. Це не пояснюється чітко, але випливає з документів Meteor:

Коли ви підписуєтесь на набір записів, він повідомляє серверу надсилати записи клієнту. Клієнт зберігає ці записи в місцевих збірниках Minimongo, з тим же ім'ям, що і collectionаргумент використовується в опубліковувати обробник added, changedі removedзворотні виклики. Meteor буде чергувати вхідні атрибути, доки ви не оголосите Mongo.Collection на клієнті з відповідним іменем колекції.

Що не пояснило , що відбувається , коли ви НЕ явно використовувати added, changedі removed, або публікувати обробник взагалі - що велика частина часу. У цьому найпоширенішому випадку аргумент collection (як не дивно) береться з імені колекції MongoDB, яку ви оголосили на сервері на кроці 1. Але це означає, що ви можете мати різні публікації та підписки з різними іменами, і всі записи опиняться в тій же колекції на клієнті. До рівня полів верхнього рівня , Meteor піклується про те, щоб виконати набір об'єднань між документами, таким чином, щоб підписки могли перекриватися - публікувати функції, які доставляють різні поля верхнього рівня до роботи клієнта поруч і на клієнті, документ в колекція будеоб'єднання двох наборів полів .

Приклад: кілька підписок, що заповнюють одну і ту ж колекцію на клієнті

У вас є колекція BlogPosts, яку ви заявляєте однаково як на сервері, так і на клієнті, хоча вона робить різні речі:

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

Клієнт BlogPostsможе отримати записи від:

  1. передплата на останні 10 публікацій у блозі

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. передплата на повідомлення поточного користувача

    // 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. передплата на найпопулярніші публікації

  4. тощо

Всі ці документи надходять із postsколекції в MongoDB, через BlogPostsколекцію на сервері, і потрапляють до BlogPostsколекції на клієнті.

Тепер ми можемо зрозуміти, чому вам потрібно зателефонувати find()більше одного разу - вдруге на клієнта, оскільки документи з усіх підписок потраплять в одну колекцію, і вам потрібно отримати лише ті, кого ви турбуєте. Наприклад, щоб отримати останні повідомлення на клієнті, ви просто віддзеркалюєте запит із сервера:

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

Це поверне курсор до всіх документів / записів, які клієнт отримав дотепер, як верхніх повідомлень, так і публікацій користувача. ( спасибі Джеффрі ).


10
Це чудово. Можливо, варто згадати те, що відбувається, якщо ви робите BlogPosts.find({})на клієнті після підписки на обидві публікації, тобто він повертає курсор усіх документів / записів, що перебувають на даний момент у клієнта, як верхніх повідомлень, так і публікацій користувача. Я бачив інші запитання щодо SO, де запитувача це бентежило.
Джеффрі Бут,

3
Це чудово. Дякую. Крім того, колекція Meteor.users () стає трохи заплутаною, оскільки вона автоматично публікується на стороні клієнта. Чи можна трохи додати до наведеної вище відповіді, щоб вказати колекцію users ()?
Jimmy MG Lim

3
Навіть якщо набагато більше, ніж спочатку просили, я думаю, що @DVG повинен позначити цей чудовий запис як прийняту відповідь. Дякую Ден.
фізіокодер

1
Дякую @DanDascalescu, чудове пояснення, яке для мене багато що прояснило, єдине, що при перегляді метеорних документів про "колекції" після прочитання вашого пояснення я думаю, що BlogPostsце не колекція, а повернутий об'єкт, який має такі методи, як "вставити", "оновити "..etc, і справжня колекція також postsу клієнті та на сервері.
UXE

4
Чи можна зателефонувати лише для набору записів, на який ви підписалися? Як і в, чи можна отримати безпосередньо запис, встановлений у моєму javascript, замість того, щоб запитувати дБ Minimongo локально?
Jimmy Knoot

27

Так, клієнтська функція find () повертає лише документи, які знаходяться на клієнті в Minimongo. З документів :

На клієнті створюється екземпляр Minimongo. Minimongo - це, по суті, непостійна реалізація Mongo в чистому JavaScript. Він служить локальним кешем, який зберігає лише підмножину бази даних, з якою працює цей клієнт. Запити на клієнті (find) подаються безпосередньо з цього кешу, не спілкуючись із сервером.

Як ви вже сказали, у публікації () вказується, які документи матиме клієнт.


1

Тут є основне правило великого пальця, publishа subscribedімена змінних повинні бути однаковими на стороні клієнта та сервера.

Імена колекцій на базі даних Mongo DB та на стороні клієнта повинні бути однаковими.

Припустимо, що я використовую публікацію та підписку на свою колекцію, названу employeesтоді кодом


сторона сервера

Тут використання varключового слова необов’язкове (використовуйте це ключове слово, щоб зробити колекцію локальною для цього файлу).

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

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

файл .js на стороні клієнта

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

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

клієнтський файл .html

Тут ми можемо використовувати subcribedDataNotAvailableдопоміжний метод, щоб дізнатись, чи готові дані на стороні клієнта, чи готові дані, то надрукуйте номери співробітників за допомогою employeeNumbersдопоміжного методу.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.