1. 背景開発プロセスでは、常に多くのリストが表示されることになります。この規模のリストがブラウザにレンダリングされると、最終的にはブラウザのパフォーマンスが低下します。データ量が多すぎると、まずレンダリングが非常に遅くなり、次にページがすぐに停止してしまいます。もちろん、それを回避する他の方法を選択することもできます。たとえば、ページングやファイルのダウンロードなどです。ここでは、仮想リストを使用してこの問題を解決する方法について説明します。 2. バーチャルリストとは何か最も簡単な説明: リストがスクロールすると、表示領域内のレンダリング要素が変更されます。 [リストの合計の高さ]と[視覚化領域の高さ]は、[単一のデータ項目の推定高さ]によって計算されます。そして、必要に応じて[視覚化領域の高さ]内でリストをレンダリングします。 3. 関連概念の紹介以下では、コンポーネント内の非常に重要なパラメータ情報を紹介します。まずここで理解して印象をつかんでおくと、後で使用するときにわかりやすくなります。
4. 仮想リストの実装仮想リストは、リストがスクロールされると、[視覚化領域の高さ] 内のレンダリング要素が変更される、と簡単に理解できます。上記で紹介した関連概念に従って、これらのプロパティに基づいて次の手順に従います。
上記の導入手順に従って、仮想リストの実装を開始しましょう。 4.1 ドライバー開発: パラメータ分析
4.1.1 アイテムレンダリング React をインポートし、{useState} を 'react' から取得します。 'biz-web-library' から {VirtualList} をインポートします。 // 各データを表示するためのコンポーネントを定義します。const ItemRender = ({ data }) => { dindex = parseInt(data); とします。 lineHeight = dindex % 2 ? '40px' : '80px' とします。 戻る ( <div style={{ lineHeight, background: dindex % 2 ? '#f5f5f5' : '#fff' }}> <h3>#{dindex} タイトル名</h3> <p>ページの高さに制限はなく、好きなように書けます</p> </div> ); }; ItemRenderMemo を React.memo に代入します。 4.1.2 データリストの初期化 // リストデータを初期化する const getDatas = () => { 定数データ = []; (i = 0; i < 100000; i++ とします) { datas.push(`${i} アイテム`); } データを返します。 }; 4.1.3 使い方 // 仮想リストエクスポートデフォルト()を使用する => { [リソース、setResources] = useState([]); const changeResources = () => { リソースを設定します(getDatas()); }; 戻る ( <div> <button onClick={changeResources}>クリックしてください</button> <div スタイル={{ 高さ: '400px'、 オーバーフロー: 'auto'、 境界線: '1px 実線 #f5f5f5'、 パディング: '0 10px', }} > <仮想リスト アイテムレンダラー={アイテムレンダラーメモ} リソース={リソース} 推定アイテムサイズ={60} /> </div> </div> ); }; 4.2 コンポーネントの初期化計算とレイアウト使い方がわかったので、コンポーネントの実装を始めましょう。渡されたデータ ソース リソースと推定高さ EstimationItemSize に従って、各データの初期化位置を計算します。 // 循環キャッシュリストの全体的な初期化の高さ export const initPositinoCache = ( 推定アイテムサイズ: 数値 = 32、 長さ: 数値 = 0、 ) => { インデックスを0とします。 位置 = 配列(長さ); while (インデックス < 長さ) { 位置[インデックス] = { 索引、 高さ: 推定アイテムサイズ、 上: インデックス * 推定アイテムサイズ、 下部: (インデックス++ + 1) * 推定アイテムサイズ、 }; } ポジションを返す。 }; リスト内の各データの高さが一貫している場合、この高さは変更されません。各データの高さが固定されていない場合は、スクロール処理中に位置が更新されます。初期化する必要があるその他のパラメータは次のとおりです。
実際、それぞれの属性については、簡単な紹介をすればその重要性がはっきりとわかります。ただし、[startOffset]パラメータを詳しく導入する必要があります。スクロール処理中に無限スクロールをシミュレートする重要なプロパティです。その値は、スクロール処理中の上からの位置を示します。 [startOffset]は[visibleData]を組み合わせることで無限スクロールの効果を実現します。 // すべての項目の位置をキャッシュします。let positions: Array<PositionType>; クラスVirtualListはReact.PureComponentを拡張します{ コンストラクタ(props) { スーパー(小道具); const {リソース} = this.props; // キャッシュを初期化する positions = initPositinoCache(props.estimatedItemSize, resources.length); この状態 = { リソース、 開始オフセット: 0, listHeight: getListHeight(positions), // 位置の最後のデータの下部属性 scrollRef: React.createRef(), // 仮想リストコンテナ参照 items: React.createRef(), // 仮想リスト表示領域参照 visibleCount: 10, // ページ上の表示領域の数 startIndex: 0, // 表示領域の開始インデックス endIndex: 10, // // 表示領域の終了インデックス }; } // TODO: 他の機能を非表示にします。 。 。 。 。 //レイアウトレンダリング() { const { ItemRender = ItemRenderComponent、 extension } = this.props; const { listHeight、startOffset、resources、startIndex、endIndex、items、scrollRef } = this.state; visibleData を resources.slice(startIndex, endIndex); とします。 戻る ( <div ref={scrollRef} スタイル={{ 高さ: `${listHeight}px` }}> <ul ref={アイテム} スタイル={{ 変換: `translate3d(0,${startOffset}px,0)`, }} > {visibleData.map((データ, インデックス) => { 戻る ( <li key={data.id || data.key || index} data-index={`${startIndex + index}`}> <ItemRender data={data} {...extrea}/> </li> ); })} </ul> </div> ); } } 4.3 スクロールすると登録イベントと更新がトリガーされます[componentDidMount] を通じて onScroll を DOM に登録します。スクロール イベントでは、requestAnimationFrame が使用されます。このメソッドは、ブラウザのアイドル時間を利用して実行されるため、コードのパフォーマンスが向上します。より深く理解したい場合は、この API の具体的な使用方法を確認してください。 コンポーネントマウント() { イベントをオン(this.getEl(), 'スクロール', this.onScroll, false); events.on(this.getEl(), 'マウスホイール', NOOP, false); // レンダリングに基づいて最新のノードを計算します。let visibleCount = Math.ceil(this.getEl().offsetHeight / EstimationItemSize); 可視カウント === this.state.visibleCount || 可視カウント === 0 の場合 { 戻る; } // visibleCount が変更されたため、endIndex、listHeight/offset を更新します。this.updateState({ visibleCount, startIndex: this.state.startIndex }); } getEl = () => { el = this.state.scrollRef || this.state.items とします。 parentEl: any = el.current?.parentElement; とします。 スイッチ(window.getComputedStyle(parentEl)?.overflowY) { ケース 'auto': ケース 'スクロール': ケース 'オーバーレイ': '可視'の場合: parentEl を返します。 } document.body を返します。 }; スクロールのオン = () => { リクエストアニメーションフレーム(() => { scrollTop を this.getEl() とします。 startIndex = binarySearch(positions, scrollTop); とします。 // startIndex が変更されたため、endIndex、listHeight/offset を更新します。this.updateState({ visibleCount: this.state.visibleCount, startIndex}); }); }; 次に、重要なステップを分析します。スクロールすると、現在の [scrollRef] 仮想リスト コンテナーの [scrollTop] を取得できます。この距離と [positions] (各項目のすべての位置プロパティを記録) を通じて、その位置の startIndex を取得できます。パフォーマンスを向上させるために、バイナリ検索を使用します。 // ツール関数、ツールファイルに格納 export const binarySearch = (list: Array<PositionType>, value: number = 0) => { 開始します: 番号 = 0; 終了: number = list.length - 1; tempIndex = null とします。 (開始 <= 終了) の間 { midIndex = Math.floor((start + end) / 2);とします。 midValue = list[midIndex].bottom;とします。 // 値が等しい場合は、見つかったノードが直接返されます(一番下なので、startIndexは次のノードである必要があります) if (midValue === 値) { midIndex + 1 を返します。 } // 中間値が入力値より小さい場合、値に対応するノードが開始値より大きいことを意味し、開始値は 1 つ前に戻ります。else if (midValue < value) { 開始 = midIndex + 1; } // 中間値が入力値より大きい場合、その値は中間値より前であることを意味し、終了ノードは中間 - 1 に移動します。 そうでない場合(中間値>値){ // tempIndexは値に最も近いすべての値を格納します if (tempIndex === null || tempIndex > midIndex) { tempIndex = 中間インデックス; } 終了 = midIndex - 1; } } tempIndex を返します。 }; startIndex を取得したら、startIndex に基づいてコンポーネント State 内のすべてのプロパティの値を更新します。 更新状態 = ({ 可視カウント、開始インデックス }) => { // 新しく計算されたノードに従ってデータを更新する this.setState({ 開始オフセット: 開始インデックス >= 1 ? 位置[開始インデックス - 1]?.bottom : 0, リストの高さ: getListHeight(位置), 開始インデックス、 表示数、 終了インデックス: getEndIndex(this.state.resources, 開始インデックス, 可視カウント) }); }; // 以下は他のファイルに配置されるツール関数です export const getListHeight = (positions: Array<PositionType>) => { index = positions.length - 1 とします。 インデックス < 0 ? 0 を返す: positions[index].bottom; }; エクスポートconst getEndIndex = ( リソース: 配列<データ>、 開始インデックス: 数値、 表示数: 数値、 ) => { resourcesLength を resources.length とします。 endIndex = startIndex + visibleCount とします。 resourcesLength > 0 を返します。Math.min(resourcesLength, endIndex) : endIndex; } 4.4 アイテムの高さが等しくない場合は更新するこの時点で、基本的な DOM スクロール、データ更新、その他のロジックは完了しました。しかし、テスト中に、高さが等しくない場合、位置やその他の操作が更新されていないことがわかりますか?これらをどこに置けばいいでしょうか? コンポーネントを更新しました() { this.updateHeight(); } 高さの更新 = () => { items: HTMLCollection = this.state.items.current?.children; とします。 if (!items.length) が return; // キャッシュを更新 updateItemSize(positions, items); // 合計の高さを更新します let listHeight = getListHeight(positions); // 合計オフセットを更新します。let startOffset = getStartOffset(this.state.startIndex, positions); this.setState({ リストの高さ、 開始オフセット、 }); }; // 以下は他のファイルに配置されるツール関数です export const updateItemSize = ( 位置: 配列<位置タイプ>, アイテム: HTMLコレクション、 ) => { Array.from(items).forEach(item => { index = Number(item.getAttribute('data-index')); とします。 高さを item.getBoundingClientRect() に設定します。 oldHeight = positions[index].height;とします。 // 差異がある場合は、このノード以降のすべてのノードを更新します。let dValue = oldHeight - height; if (dValue) { 位置[インデックス].bottom = 位置[インデックス].bottom - dValue; 位置[インデックス].height = 高さ; (k = インデックス + 1 とします; k < positions.length; k++) { 位置[k].top = 位置[k - 1].bottom; 位置[k].bottom = 位置[k].bottom - dValue; } } }); }; //現在のオフセットを取得する export const getStartOffset = ( 開始インデックス: 数値、 位置: Array<PositionType> = [], ) => { startIndex >= 1 ? positions[startIndex - 1]?.bottom : 0 を返します。 }; エクスポートconst getListHeight = (positions: Array<PositionType>) => { index = positions.length - 1 とします。 インデックス < 0 ? 0 を返す: positions[index].bottom; }; 4.5 外部パラメータデータの変更、コンポーネントデータの更新この最後のステップでは、渡した外部データ ソースが変更された場合、データを同期する必要があります。この操作は、もちろん getDerivedStateFromProps メソッドで完了します。 静的 getDerivedStateFromProps() 次のプロパティ: 仮想リストプロパティ、 前の状態: 仮想リスト状態、 ){ const { リソース、estimatedItemSize } = nextProps; if (リソース !== prevState.resources) { 位置 = initPositinoCache(推定アイテムサイズ、リソースの長さ); // 高さを更新します let listHeight = getListHeight(positions); // 合計オフセットを更新します。let startOffset = getStartOffset(prevState.startIndex, positions); endIndex = getEndIndex(resources, prevState.startIndex, prevState.visibleCount); とします。 戻る { リソース、 リストの高さ、 開始オフセット、 終了インデックス、 }; } null を返します。 } 5 結論これで、完全な仮想リスト コンポーネントが完成しました。各データの ItemRender のレンダリング関数がカスタマイズされているため、リスト形式であれば、実質的にどの項目でもスクロールできます。もちろん、オンラインで読んだ情報によると、ネットワークの問題により、画像のスクロールではリスト項目の実際の高さを保証できず、不正確さが生じる可能性があります。これについては今のところここでは説明しません。興味のある方はさらに詳しく説明してください。 React 仮想リストの実装に関するこの記事はこれで終わりです。React 仮想リストに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き閲覧してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL 8.0.12 のインストールと設定方法のグラフィック チュートリアル (Windows10)
>>: VMware 仮想化 KVM のインストールと展開のチュートリアルの概要
Supervisor は非常に優れたデーモン管理ツールです。自動起動、ログ出力、自動ログカットなど、...
この記事は、WeChat アプレットを使用して作成された簡単な計算機です。興味のある方はご覧ください...
目次Linux - ファイル記述子、ファイルポインタ、インデックスノード1. Linux - ファイ...
1 はじめに優れたコーディング習慣は優れたプログラマーが備えるべき資質ですが、コードの品質を保証する...
目次埋め込みJavaScriptと外部リンクの基本的な応用JavaScript の記述方法には、イン...
ステップ1: ディレクトリに入ります: cd /etc/mysql、debian.cnfファイルを表...
導入クロージャは JavaScript の非常に強力な機能です。いわゆるクロージャは関数内の関数です...
1. 時間差関数(TIMESTAMPDIFF、DATEDIFF) MySQLを使用して時間差を計算...
この記事では、シンプルなカレンダー効果を実現するためのjsの具体的なコードを参考までに共有します。具...
まだ rem フレキシブルレイアウトを使用していますか?圧縮された js コードの大きなセクションを...
MySQLをアンインストールする1. コントロールパネルで、MySQLのすべてのコンポーネントをア...
1. まず、Springbootを使用して簡単なDubboテストプログラムを構築し、関連する依存関係...
目次条件付きコンパイルページレイアウト要約する条件付きコンパイル条件付きコンパイルでは、特別なコメン...
目次序文1. まず、既存のバージョンの MySQL を完全にアンインストールします。 2. deb ...
目次道具プラグインをインストールするプロジェクトのルートディレクトリに.postcssrc.jsファ...