ทำความเข้าใจเกี่ยวกับ Meteor Publish / Subscribe


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

คอลเลกชัน, สิ่งพิมพ์และการสมัครเป็นพื้นที่หากินของดาวตกที่เอกสารจะหารือในรายละเอียดมากขึ้นเพื่อหลีกเลี่ยงบ่อย สับสนซึ่งบางครั้งได้รับการขยายโดยการทำให้เกิดความสับสนคำศัพท์

นี่คือSacha Greif (ผู้ร่วมเขียนDiscoverMeteor ) อธิบายสิ่งพิมพ์และการสมัครสมาชิกในสไลด์เดียว:

การสมัครรับข้อมูล

เพื่อให้เข้าใจอย่างถูกต้องว่าทำไมคุณต้องโทรfind()มากกว่าหนึ่งครั้งคุณต้องเข้าใจว่าคอลเล็กชันสิ่งพิมพ์และการสมัครรับข้อมูลทำงานอย่างไรใน Meteor:

  1. คุณกำหนดคอลเลกชันใน MongoDB ยังไม่มี Meteor ที่เกี่ยวข้อง คอลเลกชันเหล่านี้ประกอบด้วยบันทึกฐานข้อมูล (เรียกอีกอย่างว่า "เอกสาร" โดยทั้ง Mongo และ Meteorแต่ "เอกสาร" มีความกว้างมากกว่าบันทึกฐานข้อมูลตัวอย่างเช่นข้อกำหนดการอัปเดตหรือตัวเลือกคิวรีก็เป็นเอกสารเช่นกัน - วัตถุ JavaScript ที่มีfield: valueคู่)

  2. จากนั้นคุณกำหนด คอลเลกชัน บนเซิร์ฟเวอร์ Meteorด้วยไฟล์

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

    คอลเลกชันเหล่านี้ประกอบด้วย ข้อมูลทั้งหมดจากคอลเล็กชัน MongoDB และคุณสามารถเรียกใช้งานMyCollection.find({...})ได้ซึ่งจะส่งคืนเคอร์เซอร์ (ชุดของระเบียนพร้อมวิธีการวนซ้ำและส่งคืน)

  3. เคอร์เซอร์นี้คือ (โดยส่วนใหญ่) ใช้เพื่อเผยแพร่ (ส่ง) ชุดของระเบียน (เรียกว่า"ชุดระเบียน" ) คุณสามารถเลือกที่จะเผยแพร่เฉพาะบางฟิลด์จากเรกคอร์ดเหล่านั้น เป็นชุดระเบียน ( ไม่ใช่คอลเลกชัน) ที่ไคลเอนต์สมัครเพื่อ การเผยแพร่ทำได้โดยฟังก์ชั่นการเผยแพร่ซึ่งเรียกทุกครั้งที่ลูกค้าใหม่สมัครสมาชิกและสามารถใช้พารามิเตอร์เพื่อจัดการระเบียนที่จะส่งคืน (เช่นรหัสผู้ใช้เพื่อส่งคืนเฉพาะเอกสารของผู้ใช้รายนั้น)

  4. ในไคลเอนต์คุณมีคอลเลกชันMinimongo บางส่วนสะท้อนบางส่วนของการบันทึกจากเซิร์ฟเวอร์ "บางส่วน" เนื่องจากอาจมีเพียงบางฟิลด์และ "บางระเบียน" เนื่องจากโดยปกติคุณต้องการส่งไปยังไคลเอ็นต์เฉพาะระเบียนที่ต้องการเพื่อเพิ่มความเร็วในการโหลดหน้าเว็บและเฉพาะข้อมูลที่จำเป็นและได้รับอนุญาต เข้าไป.

    Minimongo เป็นการนำ Mongo มาใช้ในหน่วยความจำแบบไม่ถาวร ทำหน้าที่เป็นแคชภายในเครื่องที่จัดเก็บเฉพาะส่วนย่อยของฐานข้อมูลที่ไคลเอ็นต์นี้กำลังทำงานอยู่ แบบสอบถามบนไคลเอนต์ (ค้นหา) จะถูกส่งออกจากแคชนี้โดยตรงโดยไม่ต้องพูดคุยกับเซิร์ฟเวอร์

    คอลเลกชัน 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 คอลเลกชันบนไคลเอนต์ด้วยชื่อคอลเลคชันที่ตรงกัน

สิ่งที่ไม่สามารถอธิบายได้คือสิ่งที่เกิดขึ้นเมื่อคุณไม่ได้อย่างชัดเจนใช้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 ซึ่งผู้ถามสับสนกับเรื่องนี้
Geoffrey Booth

3
นี่มันเยี่ยมมาก ขอบคุณ. นอกจากนี้คอลเลกชัน Meteor.users () ทำให้เกิดความสับสนเล็กน้อยเนื่องจากมีการเผยแพร่โดยอัตโนมัติทางฝั่งไคลเอ็นต์ สามารถเพิ่มบิตในคำตอบด้านบนเพื่อระบุคอลเล็กชันผู้ใช้ () ได้หรือไม่
Jimmy MG Lim

3
แม้ว่าจะมีมากกว่าที่ขอไว้ในตอนแรกฉันคิดว่า @DVG ควรทำเครื่องหมายการเขียนที่ยอดเยี่ยมนี้เป็นคำตอบที่ยอมรับ ขอบคุณแดน
physiocoder

1
ขอบคุณ @DanDascalescu คำอธิบายที่ยอดเยี่ยมสำหรับฉันมากสิ่งเดียวที่เมื่อทำตามเอกสารดาวตกเกี่ยวกับ "คอลเล็กชัน" หลังจากอ่านคำอธิบายของคุณฉันคิดว่าBlogPostsไม่ใช่คอลเล็กชัน แต่เป็นวัตถุที่ส่งคืนซึ่งมีวิธีการเช่น "แทรก" "อัปเดต ".. ฯลฯ และคอลเลกชันจริงก็อยู่postsในไคลเอนต์และเซิร์ฟเวอร์เช่นกัน
UXE

4
เป็นไปได้ไหมที่จะเรียกเฉพาะชุดบันทึกที่คุณสมัคร? เป็นไปได้ไหมที่จะรับชุดระเบียนใน javascript ของฉันโดยตรงแทนที่จะค้นหาฐานข้อมูล Minimongo ในเครื่อง
Jimmy Knoot

27

ใช่ฝั่งไคลเอ็นต์ find () ส่งคืนเฉพาะเอกสารที่อยู่บนไคลเอนต์ใน Minimongo จากเอกสาร :

บนไคลเอนต์อินสแตนซ์ Minimongo ถูกสร้างขึ้น Minimongo เป็นการนำ Mongo มาใช้ในหน่วยความจำแบบไม่ถาวร ทำหน้าที่เป็นแคชภายในเครื่องที่จัดเก็บเฉพาะส่วนย่อยของฐานข้อมูลที่ไคลเอ็นต์นี้กำลังทำงานอยู่ แบบสอบถามบนไคลเอนต์ (ค้นหา) จะถูกส่งออกจากแคชนี้โดยตรงโดยไม่ต้องพูดคุยกับเซิร์ฟเวอร์

ดังที่คุณกล่าวคือการเผยแพร่ () ระบุเอกสารที่ลูกค้าจะมี


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>

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.