<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                --- title: 分頁 slug: pagination date: 0012/01/01 number: 12 points: 10 photoUrl: http://www.flickr.com/photos/ikewinski/8625379401/ photoAuthor: Mike Lewinski contents: 進一步學習 Meteor 的訂閱, 學習如何控制數據訂閱。|實現無限風格分頁|使用包 `iron-router-progress` 實現一個 iOS 風格的進度條。|為直接指向帖子的鏈接創建特殊的訂閱。 paragraphs: 67 --- Microscope 的功能看起來不錯。我們可以想象當它 release 之后會很受歡迎。 因此我們需要考慮一下隨著新帖子越來越多所帶來的性能問題。 之前我們說過客戶端集合會包含服務器端數據的一個子集。我們在帖子和評論集合已經實現了這些。 但是現在,如果我們還是一口氣發布所有帖子給所有的連接用戶。當有成千上萬的新帖子時,這會帶來一些問題。為了解決這些,我們需要給帖子分頁。 ### 添加更多的帖子 首先是我們的初始化數據,我們需要添加足夠的帖子來使分頁有意義: ~~~js // Fixture data if (Posts.find().count() === 0) { //... Posts.insert({ title: 'The Meteor Book', userId: tom._id, author: tom.profile.name, url: 'http://themeteorbook.com', submitted: new Date(now - 12 * 3600 * 1000), commentsCount: 0 }); for (var i = 0; i < 10; i++) { Posts.insert({ title: 'Test post #' + i, author: sacha.profile.name, userId: sacha._id, url: 'http://google.com/?q=test-' + i, submitted: new Date(now - i * 3600 * 1000), commentsCount: 0 }); } } ~~~ <%= caption "server/fixtures.js" %> <%= highlight "15~24" %> 運行完 `meteor reset` 重啟你的 app, 你會看到如下: <%= screenshot "12-1", "顯示初始化數據。" %> <%= commit "12-1", "添加足夠的帖子使分頁有必要。" %> ### 無限分頁 我們將實現一個"無限"的分頁。意思是在第一屏顯示 10 條帖子和一個在底部顯示的 "load more" 鏈接。點擊 "load more" 鏈接再加載另外 10 條帖子,諸如此類*無限的加載*。這意味著我們只用一個參數來實現分頁,控制在屏幕上顯示帖子的數量。 現在需要一個方法告訴服務器端返回給客戶端帖子的數量。這些發生在路由訂閱`帖子`的過程,我們會利用路由來實現分頁。 最簡單的限制返回帖子數量的方式是將返回數量加到 URL 中,如 `http://localhost:3000/25`。使用 URL 記錄數量的另一個好處是,如果不小心刷新了頁面,還會返回 25 條帖子。 為了恰當的實現分頁,我們需要修改帖子的訂閱方法。就像我們之前在*評論*那章做的,我們需要將訂閱部分的代碼從 *router* 級變為 *route* 級。 這個改變內容會比較多,通過代碼可以看的比較清楚。 首先,停止 `Router.configure()` 代碼塊中的 `posts` 訂閱。即刪除 `Meteor.subscribe('posts')`,只留下 `notifications` 訂閱: ~~~js Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'loading', notFoundTemplate: 'notFound', waitOn: function() { return [Meteor.subscribe('notifications')] } }); ~~~ <%= caption "lib/router.js" %> <%= highlight "6" %> 我們在路由路徑中加入參數 `postsLimt`。 參數后面的 `?` 表示參數是可選的。這樣路由就能同時匹配 `http://localhost:3000/50` 和 `http://localhost:3000`。 ~~~js //... Router.route('/:postsLimit?', { name: 'postsList', }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "3" %> 需要注意每個路徑都會匹配路由 `/:parameter?`。因為每個路由都會被檢查是否匹配當前路徑。我們要組織好路由來減少特異性。 話句話說,更特殊的路由會優先選擇,例如:路由 `/posts/:_id` 會在前面,而路由 `postsList` 會放到路由組的最后,因為它太泛泛了可以匹配所有路徑。 是時候處理難題了,處理訂閱和找到正確的數據。我么需要處理 `postsLimit` 參數不存在的情況。我們給它一個默認值 5, 這樣我們能更好的演示分頁。 ~~~js //... Router.route('/:postsLimit?', { name: 'postsList', waitOn: function() { var limit = parseInt(this.params.postsLimit) || 5; return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit}); } }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "5~8" %> 你注意到我們在訂閱 `posts` 時傳了一個 js 對象 ({sort: {submitted: -1}, limit: postsLimit}), 這個 js 對象會作為服務器端查詢方法 `Posts.find()` 的可選參數。下面是服務器端的實現代碼: ~~~js Meteor.publish('posts', function(options) { check(options, { sort: Object, limit: Number }); return Posts.find({}, options); }); Meteor.publish('comments', function(postId) { check(postId, String); return Comments.find({postId: postId}); }); Meteor.publish('notifications', function() { return Notifications.find({userId: this.userId}); }); ~~~ <%= caption "server/publications.js" %> <%= highlight "1~7" %> <% note do %> ### 傳遞參數 我們的訂閱代碼告訴服務器端,我們信任客戶端傳來的 JavaScript 對象 (在我們的例子中是 `{limit: postsLimit}`) 作為 `find()` 方法的 `options` 參數。這樣我們能通過 browser consle 來傳任何 option 對象。 在我們的例子中,這樣沒什么害處,因為用戶可以做的無非是改變帖子順序,或者修改 limit 值(這是我們想讓用戶做的)。但是對于一個 real-world app 我們必須做必要的限制! 幸好通過 `check()` 方法我們知道用戶不能偷偷加入額外的 options (例如 `fields`, 在某些情況下需要對外暴露 ducoments 的私有數據)。 然而,更安全的做法是傳遞單個參數而不是整個對象,通過這樣確保數據安全: ~~~js Meteor.publish('posts', function(sort, limit) { return Posts.find({}, {sort: sort, limit: limit}); }); ~~~ <% end %> 現在我們在 route 級訂閱數據,同樣的我們可以在這里設置數據的 context。我們要偏離一下之前的模式,我們讓 `data` 函數返回一個 js 對象而不是一個 cursor。 這樣我們可以創建一個*命名*的數據 context。我們稱之為 `posts`。 這意味著我們的數據 context 將存在于 `posts` 中,而不是簡單的在模板中隱式的存在于 `this` 中。除去這一點,代碼看起來很相似: ~~~js //... Router.route('/:postsLimit?', { name: 'postsList', waitOn: function() { var limit = parseInt(this.params.postsLimit) || 5; return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit}); }, data: function() { var limit = parseInt(this.params.postsLimit) || 5; return { posts: Posts.find({}, {sort: {submitted: -1}, limit: limit}) }; } }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "9~14" %> 因為我們在 route 級設置數據 context, 現在我們可以去掉在 `posts_list.js` 文件中 `posts` 模板的幫助方法。 我們的數據 context 叫做 `posts` (和 helper 同名),所以我們甚至不需要修改 `postsList` 模板! 下面是我們修改過的 `router.js` 代碼: ~~~js Router.configure({ layoutTemplate: 'layout', loadingTemplate: 'loading', notFoundTemplate: 'notFound', waitOn: function() { return [Meteor.subscribe('notifications')] } }); Router.route('/posts/:_id', { name: 'postPage', waitOn: function() { return Meteor.subscribe('comments', this.params._id); }, data: function() { return Posts.findOne(this.params._id); } }); Router.route('/posts/:_id/edit', { name: 'postEdit', data: function() { return Posts.findOne(this.params._id); } }); Router.route('/submit', {name: 'postSubmit'}); Router.route('/:postsLimit?', { name: 'postsList', waitOn: function() { var limit = parseInt(this.params.postsLimit) || 5; return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit}); }, data: function() { var limit = parseInt(this.params.postsLimit) || 5; return { posts: Posts.find({}, {sort: {submitted: -1}, limit: limit}) }; } }); var requireLogin = function() { if (! Meteor.user()) { if (Meteor.loggingIn()) { this.render(this.loadingTemplate); } else { this.render('accessDenied'); } } else { this.next(); } } Router.onBeforeAction('dataNotFound', {only: 'postPage'}); Router.onBeforeAction(requireLogin, {only: 'postSubmit'}); ~~~ <%= caption "lib/router.js" %> <%= highlight "6,25~37" %> <%= commit "12-2", "為 postsList 路徑添加限制。" %> 試一下我們的分頁。現在我們可以通過 URL 參數來控制頁面顯示帖子的數量,試一下 `http://localhost:3000/3`。你可以看到如下: <%= screenshot "12-2", "控制首頁顯示帖子的數量。" %> <% note do %> ### 為什么不用傳統的分頁? 為什么我們使用“無限分頁”而不用每頁顯示 10 條帖子的連續分頁,就像 Google 的搜索結果分頁一樣?這是由于 Meteor 的實時性決定的。 讓我們想象一下使用類似 Google 搜索結果的連續分頁模式,我們在第2頁,顯示的是 10 到 20 條帖子。這是碰巧有另外一個用戶刪除了前面 10 條帖子中的帖子。 因為 app 是實時的,我們的數據集會馬上變化,這樣第 10 條帖子變成了第 9 條,從當前頁面消失了,第 21 條帖子會出現在頁面中。這樣用戶會覺得沒什么原由的結果集變了! 即使我們可以容忍這種怪異的 UX, 由于技術的原因傳統的分頁還是很難實現。 讓我們回到前一個例子。我們從 `Posts` 集合中發布第 10 到 20 條帖子,但是在客戶端我們如何找到這些帖子?我們不能在客戶端選擇第 10 到 20 條帖子,因為客戶端集合只有 10 個帖子。 一個簡單的方案是服務器端發布 10 條帖子,在客戶端執行一下 `Posts.find()` 找到這 10 條發布的帖子。 這個方案在只有一個用戶訂閱的情況下有效,但是如果有多個用戶訂閱呢,下面我們會看到。 我們假設一個用戶需要第 10 到 20 條帖子,而另一個需要第 30 到 40。這樣在客戶端我們有兩個 20 條帖子,我們不能區分他們屬于哪個訂閱。 基于這些原因,我們在 Meteor 中不能使用傳統的分頁。 <% end %> ### 創建路由控制器(Route controller) 你可能已經注意到了我們代碼中重復了 `var limit = parseInt(this.params.postsLimit) || 5;` 兩次。而且硬編碼數字 5,這不是個理想的做法。雖然這不會導致世界末日,但是我們最好還是遵循 DRY 原則 (Don't Repeat Yourself), 讓我們看看如何能把代碼重構的更好些。 我們將介紹 Iron Router 的一個新功能, *Route Controllers*。Route controller 是通過簡單的方式將一組路由特性打包,其他的 route 可以繼承他們。現在我們只在一個路由中使用它,在下一章我們會看到它如何派上用場。 ~~~js //... PostsListController = RouteController.extend({ template: 'postsList', increment: 5, postsLimit: function() { return parseInt(this.params.postsLimit) || this.increment; }, findOptions: function() { return {sort: {submitted: -1}, limit: this.postsLimit()}; }, waitOn: function() { return Meteor.subscribe('posts', this.findOptions()); }, data: function() { return {posts: Posts.find({}, this.findOptions())}; } }); //... Router.route('/:postsLimit?', { name: 'postsList' }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "3~18, 25" %> 讓我們一步接一步的往下看。首先,我們的創建一個繼承 `RouteController` 的控制器。然后像之前一樣設置 `template` 屬性,然后添加一個新的 `increment` 屬性。 然后我們定義一個 `postsLimit` 函數用來返回當前限制的數量,然后定義一個 `findOptions` 函數用來返回 options 對象。這看起來像是個對于的步驟,但是我們后面會用到它。 接下來我們定義 `waitOn` 和 `data` 函數,除了他們現在會用到新的 `findOptions` 函數外其余和之前相同。 因為我們的控制器叫做 `PostsListController` 路由叫做 `postsList`, Iron Router 會自動使用他們。因此我們只需要從路由定義中移除 `waitOn` 和 `data` (因為路由已經會處理他們了)。如果我們需要給路由起別的名字,我們可以使用 `controller` 選項(我們將在下一章看到一個例子)。 <%= commit "12-3", "重構 postsLists 路由為 RouteController." %> ### 添加加載更多鏈接 我們現在實現了分頁,代碼看起來還不錯。只有一個問題:我們的分頁需要手工修改 URL。這顯然不是一個好的用戶體驗,現在讓我們來修改它。 我們要做的很簡單。我們將在帖子列表的下面加一個 "load more" 按鈕,點擊按鈕將增加 5 條帖子。如果當前的 URL 是 `http://localhost:3000/5`, 點擊 "load more" 按鈕 URL 將變成 `http://localhost:3000/10`。如果你之前已經實現過這種功能,我們相信你很強! 因為在前面,我們的分頁邏輯是在 route 中。記得我們是什么時候顯式命名數據上下文,而非使用匿名 cursor 的么? 沒有規則說我們的 `data` 函數只能使用 cursors, 因此,我們將用同樣的技巧來生成 "load more" 按鈕的 URL。 ~~~js //... PostsListController = RouteController.extend({ template: 'postsList', increment: 5, postsLimit: function() { return parseInt(this.params.postsLimit) || this.increment; }, findOptions: function() { return {sort: {submitted: -1}, limit: this.postsLimit()}; }, waitOn: function() { return Meteor.subscribe('posts', this.findOptions()); }, posts: function() { return Posts.find({}, this.findOptions()); }, data: function() { var hasMore = this.posts().count() === this.postsLimit(); var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment}); return { posts: this.posts(), nextPath: hasMore ? nextPath : null }; } }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "15~25" %> 讓我們來深入的看一下 router 帶來的魔術。記住 `postsList` route (它將繼承 `PostsListController` 控制器) 使用一個 `postsLimit` 參數。 因此當我們給 `this.route.path()` 傳遞參數 `{postsLimit: this.postsLimit() + this.increment}` 時,我們告訴 `postsList` route 使用這個 js 對象做數據上線文建立自己的 path。 換句話說,這和使用 `{{pathFor 'postsList'}}` Spacebars 幫助方法一樣, 除了我們用自己的數據上下文替換了隱式的 `this`。 我們使用這個路徑并將它添加到我們模板的數據上下文中,但是*只有*多條帖子時會顯示。我們的實現方法有一點小花招。 我們知道 `this.limit()` 方法會返回當前我們想要顯示帖子的數量,它可能是當前 URL 中的值,如果 URL 中沒有參數它會是默認值 (5)。 另一方面, `this.posts` 引用當前的 cursor, 因此 `this.posts.count()` 的值是在 cursor 中帖子的數量。 因此我們說當我們要求發揮 `n` 條帖子,實際返回了 `n` 條帖子,我們將繼續顯示 "load more" 按鈕。但是如果我們要求返回 `n` 條帖子,而實際返回的數量比 `n` 少,這樣我們就知道記錄已經到頭了,我們就不再顯示加載按鈕。 這就是說,我們的系統在一種情況下會有點問題:當我們的數據庫*恰好*有 `n` 條記錄時。如果是這樣,當客戶端要求返回 `n` 條帖子,我們得到了 `n` 條,然后繼續顯示 "load more" 按鈕,這是我們不知道其實已經沒有記錄可以繼續返回了。 不幸的是,我們沒有好的方法去解決這個問題,因此我們不得不接受這個不算完美的實現方式。 下面剩下的就是在帖子列表下面加上 "load more" 鏈接,并且保證在還有帖子時才顯示它: ~~~html <template name="postsList"> <div class="posts"> {{#each posts}} {{> postItem}} {{/each}} {{#if nextPath}} <a class="load-more" href="{{nextPath}}">Load more</a> {{/if}} </div> </template> ~~~ <%= caption "client/templates/posts/posts_list.html" %> <%= highlight "7~10" %> 下面是你帖子列表現在看上去的樣子: <%= screenshot "12-3", "“load more” 按鈕。 " %> <%= commit "12-4", "添加 nextPath() 到控制器中并使用它來取得更多帖子。" %> ### 更好的用戶體驗 現在我們的分頁可以工作了,但是有個煩人小問題: 每次我們點擊 "load more" 按鈕向 router 加載更多的帖子時,Iron Router 的 `waitOn` 特性會在我們等待時顯示 `loading` 模板。當結果到來時我們又會回到頁面的頂端,我們每次都要滾動頁面回到之前看的位置。 因此,首先我們要告訴 Iron Router 不要 `waintOn` 訂閱,我們將定義自己的訂閱在一個 `subscriptions` hook 中。 注意我們我們不是在 hook 中*返回*這個訂閱。返回它(這是一般 `訂閱` hook 常做的工作)將觸發一個全局的 loading hook, 這正是我們想要避免的。我們只是想在 `subscriptions` hook 中定義我們的訂閱,就像使用一個 `onBeforeAction` hook。 我們還要在我們的數據上下文中傳入一個 `ready` 變量,它指向 `this.postsSub.ready`。它會告訴我們帖子訂閱何時加載完畢。 ~~~js //... PostsListController = RouteController.extend({ template: 'postsList', increment: 5, postsLimit: function() { return parseInt(this.params.postsLimit) || this.increment; }, findOptions: function() { return {sort: {submitted: -1}, limit: this.postsLimit()}; }, subscriptions: function() { this.postsSub = Meteor.subscribe('posts', this.findOptions()); }, posts: function() { return Posts.find({}, this.findOptions()); }, data: function() { var hasMore = this.posts().count() === this.postsLimit(); var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment}); return { posts: this.posts(), ready: this.postsSub.ready, nextPath: hasMore ? nextPath : null }; } }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "12~14, 23" %> 我們將在模板中檢查 `ready` 變量的狀態,并在加載帖子時在帖子列表的下面顯示一個加載圖標(spinner): ~~~html <template name="postsList"> <div class="posts"> {{#each posts}} {{> postItem}} {{/each}} {{#if nextPath}} <a class="load-more" href="{{nextPath}}">Load more</a> {{else}} {{#unless ready}} {{> spinner}} {{/unless}} {{/if}} </div> </template> ~~~ <%= caption "client/templates/posts/posts_list.html" %> <%= highlight "10~12" %> <%= commit "12-5", "添加加載圖標使分頁更好。" %> ### 訪問任何帖子 現在我們默認每次加載 5 條新帖子,但是當用戶訪問某個帖子的單獨頁面時會發生什么? <%= screenshot "12-4", "一個空模板。" %> 試一下,我們會得到一個 "not found" 錯誤。這是有原因的: 我們告訴 router 當我們加載 `postList` route 時訂閱 `帖子` 發布。但是我們沒有說訪問 `postPage` route 時該做什么。 但是到目前,我們知道如何訂閱一個 `n` 個最新帖子的列表。我們如何向服務器端要求單個具體帖子的內容? 我們將告訴你一個小秘密: 對于一個 collection 你可以有多個 publication! 讓我們找回丟失的帖子,我們定義一個新的 publication `singlePost`,它只發布一個帖子,用 `_id` 鑒別。 ~~~js Meteor.publish('posts', function(options) { return Posts.find({}, options); }); Meteor.publish('singlePost', function(id) { check(id, String) return Posts.find(id); }); //... ~~~ <%= caption "server/publications.js" %> <%= highlight "5~7" %> 現在,讓我們在客戶端訂閱正確的帖子。我們已經在 `postPage` route 的 `wainOn` 函數中訂閱了 `comments` 發布,因此我們可以也在這里加入 `singlePost` 訂閱。讓后別忘了在 `postEdit` route 中加入我們的訂閱, 因為那里也需要相同的數據: ~~~js //... Router.route('/posts/:_id', { name: 'postPage', waitOn: function() { return [ Meteor.subscribe('singlePost', this.params._id), Meteor.subscribe('comments', this.params._id) ]; }, data: function() { return Posts.findOne(this.params._id); } }); Router.route('/posts/:_id/edit', { name: 'postEdit', waitOn: function() { return Meteor.subscribe('singlePost', this.params._id); }, data: function() { return Posts.findOne(this.params._id); } }); //... ~~~ <%= caption "lib/router.js" %> <%= highlight "6~9,16~18" %> <%= commit "12-6","使用單一帖子訂閱來確保我們總會看到正確的帖子。" %> 有了分頁,我們的程序將不再受規模問題的困擾了,用戶可以加入更多的帖子。如果有某種方法可以給帖子鏈接加上等級 (rank) 不是更好么?我們將在下一章去實現它!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看