単一ページアプリケーションにおけるフロントエンドページングの実装案について簡単に述べる

7423 ワード

概要


ページングは開発で最も一般的なニーズの一つです.ページングでは、バックエンドのデータベースページングが最も多く議論されています.これは、アプリケーションのパフォーマンスに関連し、ページングというニーズの核心でもあります.フロントエンドがしなければならないのは、バックエンドから返されたデータをページに表示することであり、仕事は簡単でこまごましたものとされています.単一ページアプリケーションでは、無限スクロール、前ページ&次ページ、ページ番号が最も一般的な中分割スキームがたくさんあります.本稿では,この3つのページング方式について述べる.

汎用


どのページング・スキームを使用しても、次のような一般的なニーズを処理する必要があります.
  • urlを解析し、現在のページのパラメータ
  • を抽出する
  • 戻りデータからカスタムDOM
  • を生成する.
  • ノード内のすべてのサブエレメント
  • を除去する.
  • あるノードに要素リスト
  • を挿入する.
    //    url        
    //   http://host/items?page=5     page=5    5
    function parsePage() {
        var searchString = window.location.search.substr(1).split('&').filter(v => v.indexOf('page') !== -1)[0];
        var page = Number(searchString.split('=')[1]);
        return isNaN(page) ? 1 : page;
    }
    
    //       DOM
    // generateItemView :: Object -> DOM Node
    function generateItemView(object) { /* implementation */ }
    
    //    Node       
    function removeItems(node) {
        while (node.firstChild) {
            node.removeChild(node.firstChild);
        }
    }
    
    //   Node        
    function insertItems(node, items) {
        items.forEach(item => node.appendChild(generateItemView(item)));
    }

    次の例のコードでは、これらの関数が直接呼び出され、定義が繰り返されません.

    無限スクロール


    フロントエンドからでもバックエンドからでも、無限スクロールは最も簡単なページングスキームだと思います.バックエンドでは、pagelimitに従って直接範囲を検出し、1つの配列をフロントエンドに返すだけで、他のスキームのように総数を検索する必要はありません.フロントエンドでは,バックエンドから返されたデータに直接つなぎ合わせればよいが,バックエンドが空の配列を返すと,最後のページに達したと考えられ,バックエンドに要求する必要はない.
    //          
    // GET /items?page=5
    { items: [...] }
    //     
    function getItems(page) {
        fetch(`/items?page=${page}`)
            .then(res => res.json())
            .then(res => {
                if (res.items.length > 0) {
                    insertItems(
                        document.getElementById('container'),
                        res.items
                    );
                } else {
                    alert('No more data');
                }
            });
    }
    

    無限スクロールは簡単で、ユーザー体験もいいですが、致命的な欠点があります.
  • パフォーマンスの問題が発生しやすい
  • ブラウズの進行状況が失われやすい
  • これらの欠点を解決する方法は、パフォーマンスの問題はダイナミックレンダリングで解決できますが、ブラウズの進行状況を失うと、簡単な新しいウィンドウを開くことで解決できます.

    前ページ&次ページ


    このページング方式は無限スクロールに比べて少し複雑になります.最も重要なのは、バックエンドが合計クエリーを必要とし、現在のページ数に基づいて前のページまたは次のページをクエリーできるかどうかを計算することです.もちろん、この部分はバックエンドでもフロントエンドでも計算できます.

    バックエンド計算


    バックエンドで計算すると、バックエンドがしなければならないことがあります.
  • クエリ合計
  • 計算hasPrevおよびhasNext
  • クエリー要素リスト
  • フロントエンドは比較的簡単です.
  • は、バックエンドから戻るhasPrevおよびhasNextに基づいて、前ページ/次ページボタン
  • を表示する必要があるか否かを判断する.
  • コンテナ内のすべての要素を除去し、新しい要素(すなわち、古い要素を新しい要素に置き換える)
  • を挿入する.
    //         
    // GET /items?page=5
    {
        // hasPrev   hasNext           ,      
        hasPrev: true,
        hasNext: true,
        items: [...]
    }
    
    //     
    function getItems(page) {
        fetch(`/items?page=${page}`)
            .then(res => res.json())
            .then(res => {
                res.hasPrev
                    ? document.getElementById('prevButton').style.display = 'block'
                    : document.getElementById('prevButton').style.display = 'none';
    
                res.hasNext
                    ? document.getElementById('nextButton').style.display = 'block'
                    : document.getElementById('nextButton').style.display = 'none';
    
                var container = document.getElementById('container');
                removeItems(container);
                insertItems(container, res.items);
            });
    }

    このスキームは実装が簡単ですが、ページを分割するたびに総ページ数をクエリーし、リソースを浪費するのが欠点です.

    フロントエンド計算


    フロントエンド計算であれば、バックエンドが行うことは比較的簡単で、クエリの合計数のインタフェースを1つ提供すればよい.フロントエンドでは、現在のエンドデータが失われた場合(ユーザーがページをリフレッシュするなど)の処理案を考慮しながら、より多くのことをする必要があります.
  • 最初にページをロードするときに、クエリの合計数のインタフェースを呼び出すとともに、取得要素のインタフェース
  • を呼び出す必要がある.
  • は、データを返すhasPrevおよびhasNextを計算し、前のページ/次のページボタン
  • を表示する必要があるか否かを判断する.
  • コンテナ内のすべての要素を除去し、新しい要素(すなわち、古い要素を新しい要素に置き換える)
  • を挿入する.
    //         
    // GET /itemsCount
    { total: 100 }
    
    // GET /items?page=5
    { items: [...] }
    //     
    var total = 0;
    var limit = 10;
    
    window.onload = getItemsCount(getItems);
    
    //     
    function getItemsCount(callback) {
        fetch('/itemsCount')
            .then(res => res.json())
            .then(res => {
                total = res.total;
                callback.call(null, parsePage());
            });
    }
    
    function getItems(page) {
        fetch(`/items?page=${page}`)
            .then(res => res.json())
            .then(res => {
                var hasPrev = page != 1;
                var hasNext = page != Math.ceil(total / limit);
                hasPrev
                    ? document.getElementById('prevButton').style.display = 'block'
                    : document.getElementById('prevButton').style.display = 'none';
    
                hasNext
                    ? document.getElementById('nextButton').style.display = 'block'
                    : document.getElementById('nextButton').style.display = 'none';
    
                var container = document.getElementById('container');
                removeItems(container);
                insertItems(container, res.items);
            });
    }

    この案は後端を先端に振ることができて、先端の仕事はまたドラえもんになります!

    ページ番号


    最後にページ番号のページ分けについて話します.このシナリオは「前ページ&次ページ」のシナリオと似ていますが、現在のページと総数に基づいてページ番号を生成する必要がある点が異なります.ページ番号の生成はこのスキームで最も面倒なところです.簡単な例を挙げると、私たちのデータが50ページあると仮定すると、すべてのページ番号を表示することはできません.不連続なページ番号のセットを生成する必要があります.
    次の形式でページを表示できます.
    // ------------------------------
    //          -1         
    //     DOM    ,         
    // ------------------------------
    //        1  
    [1, 2, 3, -1, 50]
    
    //        3  
    [1, 2, 3, 4, 5, -1, 50]
    
    //        25  
    [1, -1, 23, 24, 25, 26, 27, -1, 50]
    
    //        48  
    [1, -1, 46, 47, 48, 49, 50]
    
    //        50  
    [1, -1, 48, 49, 50]

    ページ番号を生成する原則は、通常、次のとおりです.
  • 最初のページと最後のページは
  • を表示する必要があります.
  • 他のページは、通常、現在のページの前後2ページ(すなわちx+-2)
  • である必要に応じて表示される.
  • ページ数が10ページ未満の場合は、そのまますべてのページ番号が表示されます(なぜ10ページなのか?実は前の2つの原則を満たしている場合は、7ページの省略番号さえ正常に表示されます.しかし、ページ数が少ない場合は省略番号が表示されるのは変です.)
  • var lastPage = Math.ceil(total / limit);
    
    //            
    function genPages() {
    
        if (lastPage <= 10) {
            return Array(lastPage).fill().map((v, i) => i + 1);
        }
    
        // dynamicPages                ,-1      
        var dynamicPages;
    
        if (page === 1) {
            dynamicPages = [2, 3, -1];
    
        } else if (page === 2) {
            dynamicPages = [2, 3, 4, -1];
    
        } else if (page === 3) {
            dynamicPages = [2, 3, 4, 5, -1];
    
        } else if (page === lastPage - 2) {
            dynamicPages = [-1, page - 2, page - 1, page, page + 1];
    
        } else if (page === lastPage - 1) {
            dynamicPages = [-1, page - 2, page - 1, page];
    
        } else if (page === lastPage) {
            dynamicPages = [-1, page - 2, page - 1];
    
        } else {
            dynamicPages = [-1, page - 2, page - 1, page, page + 1, page + 2, -1];
        }
    
        dynamicPages.unshift(1);
        dynamicPages.push(lastPage);
    
        return dynamicPages;
    }

    ダイナミックページ番号を生成する論理は、フロントエンドにおいてもバックエンドにおいてもあまり影響がなく、自分のニーズに合わせて選択できます.他の部分の詳細については、「前のページ&次のページ」と似ていますが、ここでは繰り返しません.

    出典


    http://scarletsky.github.io/2...

    参考資料


    https://github.com/xitu/gold-...