理論と実践:理論的には理論と実践の間に違いはありませんが、実際には違いがあります。
- 理論:すべては明らかですが、何も機能しません。
- 練習:すべてが機能しますが、明確なものはありません。
- 時には理論と実践が出会います。何も機能せず、何も明らかではありません。
時々、最良のアプローチはプロトタイプであり、問題を興味深いものにするために、少し時間をかけて調理しましたが、プロトタイプとしては確かに多くのいぼがあります...
要するに、データフェッチのバックログを制限する最も簡単なソリューションは、フェッチを実行しているルーチン内に貧乏人のミューテックスを設定することです。(以下のコード例では、フェッチシミュレートされた関数であるsimulateFetchOfData
。)mutexがあれば、そのようなことは関数スコープ外変数を設定することを含むfalse
フェッチ、使用のために開放され、そして場合true
フェッチ現在進行中です。
つまり、ユーザーが水平または垂直スライダーを調整してデータのフェッチを開始すると、データをフェッチする関数は、最初にグローバル変数mutex
が真であるかどうか(つまり、フェッチが既に実行されているかどうか)を確認し、真の場合は単に終了します。 。mutex
がtrueでない場合は、trueに設定さmutex
れ、引き続きフェッチが実行されます。そしてもちろん、フェッチ関数の最後でmutex
falseに設定されているため、次のユーザー入力イベントは最初にミューテックスチェックを通過し、別のフェッチを実行します...
プロトタイプに関するいくつかのメモ。
simulateFetchOfData
関数内には、データの取得の遅延をシミュレートするPromiseとして構成されたsleep(100)があります。これには、コンソールへのロギングがいくつか含まれています。ミューテックスチェックを削除すると、スライダーを移動しているときにコンソールが開いた状態で、の多くのインスタンスsimulateFetchOfData
が開始され、スリープ(つまり、データのシミュレートされたフェッチ)を待機してサスペンス状態になり、解決されます。適所では、一度に1つのインスタンスのみが開始されます。
- スリープ時間を調整して、より大きなネットワークまたはデータベースの待ち時間をシミュレートし、ユーザーエクスペリエンスの感触をつかむことができます。たとえば、私が使用しているネットワークでは、米国本土の通信で90ミリ秒の遅延が発生します。
- もう1つの注目すべき点は、フェッチが終了したとき、および
mutex
false にリセットした後、水平スクロール値と垂直スクロール値が揃っているかどうかを確認するチェックが実行されることです。そうでない場合、別のフェッチが開始されます。これにより、フェッチがビジーのために多くのスクロールイベントが発生しない可能性があるにもかかわらず、少なくとも1つの最終フェッチをトリガーすることによって最終スクロール値に対処できます。
- シミュレートされたセルデータは、行と列の列番号の文字列値です。たとえば、「555-333」は行555、列333を示します。
- 名前
buffer
が付けられたスパース配列は、「フェッチされた」データを保持するために使用されます。コンソールで調べると、多くの「空のx XXXX」エントリが表示されます。simulateFetchOfData
関数は、データが既に保持されている場合、そのように設定されていないbuffer
場合、いかなる「フェッチ」が行われます。
(プロトタイプを表示するには、コード全体を新しいテキストファイルにコピーして貼り付け、「。html」に名前を変更して、ブラウザーで開きます。 編集: ChromeとEdgeでテストされています。)
<html><head>
<script>
function initialize() {
window.rowCount = 10000;
window.colCount = 5000;
window.buffer = [];
window.rowHeight = Array( rowCount ).fill( 25 ); // 20px high rows
window.colWidth = Array( colCount ).fill( 70 ); // 70px wide columns
var cellAreaCells = { row: 0, col: 0, height: 0, width: 0 };
window.contentGridCss = [ ...document.styleSheets[ 0 ].rules ].find( rule => rule.selectorText === '.content-grid' );
window.cellArea = document.getElementById( 'cells' );
// Horizontal slider will indicate the left most column.
window.hslider = document.getElementById( 'hslider' );
hslider.min = 0;
hslider.max = colCount;
hslider.oninput = ( event ) => {
updateCells();
}
// Vertical slider will indicate the top most row.
window.vslider = document.getElementById( 'vslider' );
vslider.max = 0;
vslider.min = -rowCount;
vslider.oninput = ( event ) => {
updateCells();
}
function updateCells() {
// Force a recalc of the cell height and width...
simulateFetchOfData( cellArea, cellAreaCells, { row: -parseInt( vslider.value ), col: parseInt( hslider.value ) } );
}
window.mutex = false;
window.lastSkippedRange = null;
window.addEventListener( 'resize', () => {
//cellAreaCells.height = 0;
//cellAreaCells.width = 0;
cellArea.innerHTML = '';
contentGridCss.style[ "grid-template-rows" ] = "0px";
contentGridCss.style[ "grid-template-columns" ] = "0px";
window.initCellAreaSize = { height: document.getElementById( 'cellContainer' ).clientHeight, width: document.getElementById( 'cellContainer' ).clientWidth };
updateCells();
} );
window.dispatchEvent( new Event( 'resize' ) );
}
function sleep( ms ) {
return new Promise(resolve => setTimeout( resolve, ms ));
}
async function simulateFetchOfData( cellArea, curRange, newRange ) {
//
// Global var "mutex" is true if this routine is underway.
// If so, subsequent calls from the sliders will be ignored
// until the current process is complete. Also, if the process
// is underway, capture the last skipped call so that when the
// current finishes, we can ensure that the cells align with the
// settled scroll values.
//
if ( window.mutex ) {
lastSkippedRange = newRange;
return;
}
window.mutex = true;
//
// The cellArea width and height in pixels will tell us how much
// room we have to fill.
//
// row and col is target top/left cell in the cellArea...
//
newRange.height = 0;
let rowPixelTotal = 0;
while ( newRange.row + newRange.height < rowCount && rowPixelTotal < initCellAreaSize.height ) {
rowPixelTotal += rowHeight[ newRange.row + newRange.height ];
newRange.height++;
}
newRange.width = 0;
let colPixelTotal = 0;
while ( newRange.col + newRange.width < colCount && colPixelTotal < initCellAreaSize.width ) {
colPixelTotal += colWidth[ newRange.col + newRange.width ];
newRange.width++;
}
//
// Now the range to acquire is newRange. First, check if this data
// is already available, and if not, fetch the data.
//
function isFilled( buffer, range ) {
for ( let r = range.row; r < range.row + range.height; r++ ) {
for ( let c = range.col; c < range.col + range.width; c++ ) {
if ( buffer[ r ] == null || buffer[ r ][ c ] == null) {
return false;
}
}
}
return true;
}
if ( !isFilled( buffer, newRange ) ) {
// fetch data!
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
buffer[ r ] = [];
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
buffer[ r ][ c ] = `${r}-${c} data`;
}
}
console.log( 'Before sleep' );
await sleep(100);
console.log( 'After sleep' );
}
//
// Now that we have the data, let's load it into the cellArea.
//
gridRowSpec = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
gridRowSpec += rowHeight[ r ] + 'px ';
}
gridColumnSpec = '';
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
gridColumnSpec += colWidth[ c ] + 'px ';
}
contentGridCss.style[ "grid-template-rows" ] = gridRowSpec;
contentGridCss.style[ "grid-template-columns" ] = gridColumnSpec;
cellArea.innerHTML = '';
for ( let r = newRange.row; r < newRange.row + newRange.height; r++ ) {
for ( let c = newRange.col; c < newRange.col + newRange.width; c++ ) {
let div = document.createElement( 'DIV' );
div.innerText = buffer[ r ][ c ];
cellArea.appendChild( div );
}
}
//
// Let's update the reference to the current range viewed and clear the mutex.
//
curRange = newRange;
window.mutex = false;
//
// One final step. Check to see if the last skipped call to perform an update
// matches with the current scroll bars. If not, let's align the cells with the
// scroll values.
//
if ( lastSkippedRange ) {
if ( !( lastSkippedRange.row === newRange.row && lastSkippedRange.col === newRange.col ) ) {
lastSkippedRange = null;
hslider.dispatchEvent( new Event( 'input' ) );
} else {
lastSkippedRange = null;
}
}
}
</script>
<style>
/*
".range-slider" adapted from... https://codepen.io/ATC-test/pen/myPNqW
See https://www.w3schools.com/howto/howto_js_rangeslider.asp for alternatives.
*/
.range-slider-horizontal {
width: 100%;
height: 20px;
}
.range-slider-vertical {
width: 20px;
height: 100%;
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical;
}
/* grid container... see https://www.w3schools.com/css/css_grid.asp */
.grid-container {
display: grid;
width: 95%;
height: 95%;
padding: 0px;
grid-gap: 2px;
grid-template-areas:
topLeft column topRight
row cells vslider
botLeft hslider botRight;
grid-template-columns: 50px 95% 27px;
grid-template-rows: 20px 95% 27px;
}
.grid-container > div {
border: 1px solid black;
}
.grid-topLeft {
grid-area: topLeft;
}
.grid-column {
grid-area: column;
}
.grid-topRight {
grid-area: topRight;
}
.grid-row {
grid-area: row;
}
.grid-cells {
grid-area: cells;
}
.grid-vslider {
grid-area: vslider;
}
.grid-botLeft {
grid-area: botLeft;
}
.grid-hslider {
grid-area: hslider;
}
.grid-botRight {
grid-area: botRight;
}
/* Adapted from... https://medium.com/evodeck/responsive-data-tables-with-css-grid-3c58ecf04723 */
.content-grid {
display: grid;
overflow: hidden;
grid-template-rows: 0px; /* Set later by simulateFetchOfData */
grid-template-columns: 0px; /* Set later by simulateFetchOfData */
border-top: 1px solid black;
border-right: 1px solid black;
}
.content-grid > div {
overflow: hidden;
white-space: nowrap;
border-left: 1px solid black;
border-bottom: 1px solid black;
}
</style>
</head><body onload='initialize()'>
<div class='grid-container'>
<div class='topLeft'> TL </div>
<div class='column' id='columns'> column </div>
<div class='topRight'> TR </div>
<div class='row' id = 'rows'> row </div>
<div class='cells' id='cellContainer'>
<div class='content-grid' id='cells'>
Cells...
</div>
</div>
<div class='vslider'> <input id="vslider" type="range" class="range-slider-vertical" step="1" value="0" min="0" max="0"> </div>
<div class='botLeft'> BL </div>
<div class='hslider'> <input id="hslider" type="range" class="range-slider-horizontal" step="1" value="0" min="0" max="0"> </div>
<div class='botRight'> BR </div>
</div>
</body></html>
繰り返しますが、これは不要なデータ呼び出しのバックログを制限する手段を証明するためのプロトタイプです。これを運用目的でリファクタリングする場合、次のような多くの領域で対処する必要があります。1)グローバル変数スペースの使用を減らす。2)行と列のラベルを追加します。3)個々の行または列をスクロールするためのボタンをスライダーに追加します。4)データ計算が必要な場合、関連データをバッファリングする可能性があります。5)など...