Общие сведения о публикации / подписке 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

Коллекции, публикации и подписки - сложная область Meteor, которую в документации можно было бы обсудить более подробно, чтобы избежать частой путаницы , которая иногда усугубляется путаницей терминологии. .

Вот Саша Грейф (соавтор 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. Он служит локальным кешем, в котором хранится только подмножество базы данных, с которой работает этот клиент. Запросы на клиенте (поиск) обслуживаются непосредственно из этого кеша, без взаимодействия с сервером.

    Эти коллекции Minimongo изначально пусты. Они заполнены

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

    звонки. Обратите внимание, что параметр для подписки не является именем коллекции; это имя набора записей, который сервер использовал в publishвызове. subscribe()Вызов выписывает клиент к набору записей - подмножество записей из коллекции сервера (например , последние 100 сообщений в блоге), со всеми или подмножеством полей в каждой записи (например , только titleи date). Как Minimongo знает, в какую коллекцию поместить входящие записи? Имя коллекции будет collectionаргументом, используемым в обработчиках публикации added, changedи removedобратных вызовах, или, если они отсутствуют (что имеет место в большинстве случаев), это будет имя коллекции MongoDB на сервере.

Изменение записей

Именно здесь Meteor делает вещи очень удобными: когда вы изменяете запись (документ) в коллекции Minimongo на клиенте, Meteor мгновенно обновляет все шаблоны, которые от него зависят, а также отправляет изменения обратно на сервер, который, в свою очередь, сохранит изменения в MongoDB и отправит их соответствующим клиентам, которые подписались на набор записей, включающий этот документ. Это называется компенсацией задержки и является одним из семи основных принципов Meteor .

Множественные подписки

У вас может быть несколько подписок, которые извлекают разные записи, но все они попадут в одну и ту же коллекцию на клиенте, если они получены из одной коллекции на сервере, в зависимости от их _id. Это не объясняется четко, но подразумевается в документации Meteor:

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

Что не объяснило , что происходит , когда вы не явно использовать added, changedи removed, или публиковать обработчик вообще - что большая часть времени. В этом наиболее распространенном случае аргумент коллекции (что неудивительно) берется из имени коллекции 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 должен отметить эту замечательную запись как принятый ответ, даже если намного больше, чем первоначально просили. Спасибо, Дэн.
Physiocoder

1
Спасибо @DanDascalescu, отличное объяснение, которое многое прояснило для меня, единственное, что, когда я следую метеоритным документам о «коллекциях» после прочтения вашего объяснения, я думаю, что BlogPostsэто не коллекция, это возвращенный объект, который имеет такие методы, как «вставить», «обновить» "... и т. д., а настоящая коллекция находится и postsна клиенте, и на сервере.
UXE

4
Можно ли вызвать только тот набор записей, на который вы подписаны? Например, можно ли напрямую получить набор записей в моем javascript вместо того, чтобы запрашивать базу данных Minimongo локально?
Джимми Нут

27

Да, find () на стороне клиента возвращает только те документы, которые находятся на клиенте в Minimongo. Из документов :

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

Как вы говорите, publish () указывает, какие документы будут у клиента.


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.