Implementierung der virtuellen React-Liste

Implementierung der virtuellen React-Liste

1. Hintergrund

Während des Entwicklungsprozesses stoßen wir immer wieder auf die Anzeige vieler Listen. Wenn Listen dieser Größenordnung im Browser gerendert werden, führt dies letztendlich zu einer Verschlechterung der Browserleistung. Ist die Datenmenge zu groß, wird erstens das Rendering extrem langsam und zweitens bleibt die Seite direkt hängen. Natürlich können Sie auch andere Wege wählen, um dies zu vermeiden. Beispielsweise Paging oder Herunterladen von Dateien und so weiter. Hier besprechen wir, wie dieses Problem mithilfe virtueller Listen gelöst werden kann.

2. Was ist eine virtuelle Liste

Die einfachste Beschreibung: Wenn die Liste scrollt, ändern Sie die Rendering-Elemente im sichtbaren Bereich.

Die [Gesamthöhe der Liste] und die [Höhe des Visualisierungsbereichs] werden durch die [geschätzte Höhe eines einzelnen Datenelements] berechnet. Und rendern Sie die Liste nach Bedarf innerhalb von [Höhe des Visualisierungsbereichs].

3. Einführung in verwandte Konzepte

Im Folgenden werden einige sehr wichtige Parameterinformationen in der Komponente vorgestellt. Lassen Sie uns diese hier zunächst verstehen, um einen Eindruck zu bekommen, damit sie bei späterer Verwendung klarer sind.

  • [Geschätzte Höhe eines einzelnen Datenelements]: Die spezifische Höhe eines bestimmten Elements in der Liste. Es kann [feste Höhe] oder [dynamische Höhe] sein.
  • [Gesamthöhe der Liste]: Wenn alle Daten gerendert sind, die [Gesamthöhe] der Liste
  • [Höhe des Visualisierungsbereichs]: Der Container hängt an der virtuellen Liste. Der sichtbare Bereich der Liste
  • [Geschätzte Anzahl der angezeigten Elemente]: In [Höhe des Visualisierungsbereichs], gemäß [Geschätzte Höhe eines einzelnen Datenelements], die Anzahl der sichtbaren Datenelemente
  • [Startindex]: [Höhe des Visualisierungsbereichs] Der Index der ersten anzuzeigenden Daten
  • [End Index]: [Visual Area Height] Der Index des zuletzt angezeigten Datenelements
  • [Cache für die Position jedes Elements]: Da die Höhe der Liste nicht festgelegt ist, wird die Höhenposition jedes Datenelements aufgezeichnet, einschließlich der Attribute Index, oben, unten und Zeilenhöhe.

4. Implementierung virtueller Listen

Die virtuelle Liste kann einfach so verstanden werden: Wenn die Liste gescrollt wird, werden die Rendering-Elemente innerhalb der [Höhe des Visualisierungsbereichs] geändert. Gemäß den oben eingeführten relevanten Konzepten befolgen wir diese Schritte basierend auf diesen Eigenschaften:

  • Übergeben Sie die Komponentendaten [Datenliste (Ressourcen)] und [geschätzte Höhe (estimatedItemSize)].
  • Berechnen Sie die Anfangsposition jedes Datenelements basierend auf [Datenliste (Ressourcen)] und [geschätzter Höhe (estimatedItemSize)] (dem Platzhalter jedes Datenelements, wenn alle gerendert werden).
  • Berechnen Sie die Gesamthöhe der Liste
  • [Höhe des visuellen Bereichs] Durch CSS gesteuert
  • Berechnen Sie anhand der [Höhe des Visualisierungsbereichs] die geschätzte Anzahl der Anzeigeelemente im Visualisierungsbereich
  • Initialisieren Sie das [Header-Mount-Element] und das [Tailer-Mount-Element] des sichtbaren Fensters. Wenn gescrollt wird, berechnen Sie das [Header-Mount-Element] und das [Tailer-Mount-Element] entsprechend der Scroll-Differenz und der Scroll-Richtung neu.

Beginnen wir gemäß den obigen Einführungsschritten mit der Implementierung einer virtuellen Liste.

4.1 Treiberentwicklung: Parameteranalyse

Parameter veranschaulichen Typ Standardwert
Ressourcen Quelldaten-Array Anordnung []
geschätzteArtikelgröße Geschätzte Höhe der einzelnen Daten Nummer 32px
extra Wird verwendet, um ItemRender anzupassen und andere Parameter zu übergeben beliebig keiner
ArtikelRender Komponenten zum Rendern der einzelnen Daten Reagieren.FC const ItemRender = ({ data }: Data) => (<React.Fragment>{String(data) }</React.Fragment>)
Schlüssel Generiert während der Durchquerung einen eindeutigen Schlüssel für das Element. Es muss ein Feld mit einem bestimmten, eindeutigen Wert in den Ressourcendaten sein. Wird zur Leistungsverbesserung verwendet. Schnur Standardmäßige Reihenfolgeanpassung -> ID -> Schlüssel -> Index

4.1.1 Artikel rendern

importiere React, { useState } von 'react';
importiere { VirtualList } aus „biz-web-library“;
// Definieren Sie die Komponente für die Anzeige der einzelnen Daten const ItemRender = ({ data }) => {
  let dindex = parseInt(Daten);
  lass lineHeight = dindex % 2 ? '40px' : '80px';
  zurückkehren (
    <div Stil = {{ Zeilenhöhe, Hintergrund: dindex % 2 ? '#f5f5f5' : '#fff' }}>
      <h3>#{dindex} Titelname</h3>
      <p>Schreiben Sie, was Sie wollen, keine Begrenzung der Seitenhöhe</p>
    </div>
  );
};
const ItemRenderMemo = React.memo(ItemRender);

4.1.2 Initialisierung der Datenliste

// Listendaten initialisieren const getDatas = () => {
  const Daten = [];
  für (sei i = 0; i < 100000; i++) {
    datas.push(`${i} Element`);
  }
  Daten zurückgeben;
};

4.1.3 Verwendung

// Virtuelle Liste verwenden export default () => {
  let [Ressourcen, setResources] = useState([]);
  const changeResources = () => {
    : Setze Ressourcen(getDatas());
  };

  zurückkehren (
    <div>
      <button onClick={changeResources}>klick mich </button>

      <div
        Stil={{
          Höhe: '400px',
          Überlauf: „auto“,
          Rahmen: '1px durchgezogen #f5f5f5',
          Polsterung: '0 10px',
        }}
      >
        <VirtuelleListe
          ItemRender={ItemRenderMemo}
          Ressourcen={Ressourcen}
          geschätzteArtikelgröße={60}
        />
      </div>
    </div>
  );
};

4.2 Berechnung und Layout der Komponenteninitialisierung

Nachdem wir nun wissen, wie man es verwendet, beginnen wir mit der Implementierung unserer Komponente. Berechnen Sie die Initialisierungsposition jedes Datenelements basierend auf den in der Datenquelle übergebenen Ressourcen und der geschätzten Höhe ( EstimatedItemSize).

// Gesamtinitialisierungshöhe der Ringcacheliste export const initPositinoCache = (
  geschätzteArtikelgröße: Zahl = 32,
  Länge: Zahl = 0,
) => {
  sei Index = 0,
  Positionen = Array(Länge);
  während (Index < Länge) {
    Positionen[Index] = {
      Index,
      Höhe: geschätzteArtikelgröße,
      oben: Index * geschätzteArtikelgröße,
      unten: (Index++ + 1) * geschätzteArtikelgröße,
    };
  }
  Positionen zurückgeben;
};

Wenn die Höhe aller Daten in der Liste konsistent ist, ändert sich diese Höhe nicht. Wenn die Höhe der einzelnen Daten nicht festgelegt ist, wird die Position während des Bildlaufvorgangs aktualisiert. Hier sind einige andere Parameter, die initialisiert werden müssen:

Parameter veranschaulichen Typ Standardwert
Ressourcen Quelldaten-Array Anordnung []
StartOffset Der Versatz des sichtbaren Bereichs von oben Nummer 0
Listenhöhe Wenn alle Daten gerendert sind, die Höhe des Containers beliebig keiner
sichtbareAnzahl Anzahl der Visualisierungsbereiche auf einer Seite Nummer 10
StartIndex Visualisierungsbereich Startindex Nummer 0
endIndex Visualisierungsbereich Endindex Nummer 10
sichtbareDaten Im Visualisierungsbereich angezeigte Daten Anordnung []

Tatsächlich ist die Bedeutung jedes Attributs nach einer kurzen Einführung klar zu erkennen. Der Parameter [startOffset] muss jedoch ausführlich eingeführt werden. Es handelt sich um eine wichtige Eigenschaft, die während des Scrollvorgangs ein unendliches Scrollen simuliert. Sein Wert gibt die Position von oben während unseres Scrollvorgangs an. [startOffset] erreicht den Effekt des unendlichen Scrollens durch die Kombination von [visibleData].
Tipps: Achten Sie hier auf die Position von [Positionen], die einer externen Variablen einer Komponente entspricht. Denken Sie daran, es nicht an die statische Eigenschaft der Komponente zu hängen.

// Zwischenspeichern Sie die Positionen aller Elemente let positions: Array<PositionType>;

Klasse VirtualList erweitert React.PureComponent{
 
  Konstruktor(Requisiten) {
    super(Requisiten);
    const { Ressourcen } = this.props;

    // Cache-Positionen initialisieren = initPositinoCache(props.estimatedItemSize, resources.length);
    dieser.Zustand = {
      Ressourcen,
      StartOffset: 0,
      listHeight: getListHeight(positions), // unteres Attribut der letzten Daten in Positionen scrollRef: React.createRef(), // virtueller Listencontainer ref
      Elemente: React.createRef(), // Virtueller Listenanzeigebereich ref
      visibleCount: 10, // Anzahl der sichtbaren Bereiche auf einer Seite startIndex: 0, // Startindex des sichtbaren Bereichs endIndex: 10, // // Endindex des sichtbaren Bereichs };
  }
  // TODO: Einige andere Funktionen ausblenden. . . . .


  //Layout rendern() {
  const { ItemRender = ItemRenderComponent, Erweiterung } = this.props;
  const { Listenhöhe, Startoffset, Ressourcen, Startindex, Endindex, Elemente, Scrollreferenz } = dieser.Zustand;
  let visibleData = resources.slice(startIndex, endIndex);

  zurückkehren (
    <div ref={scrollRef} Stil={{ Höhe: `${listHeight}px` }}>
      <ul
        ref={Artikel}
        Stil={{
          transformieren: `translate3d(0,${startOffset}px,0)`,
        }}
      >
        {visibleData.map((Daten, Index) => {
          zurückkehren (
            <li Schlüssel={Daten.ID || Daten.Schlüssel || Index} Datenindex={`${startIndex + index}`}>
              <ItemRender-Daten = {Daten} {...extrea}/>
            </li>
          );
        })}
      </ul>
    </div>
  );
  }
} 

4.3 Scrollen löst Registrierungsereignisse und Aktualisierungen aus

Registrieren Sie onScroll über [componentDidMount] im DOM. Beim Scroll-Ereignis wird requestAnimationFrame verwendet. Diese Methode nutzt die Leerlaufzeit des Browsers zur Ausführung, was die Leistung des Codes verbessern kann. Wenn Sie ein tieferes Verständnis wünschen, können Sie sich die spezifische Verwendung dieser API ansehen.

componentDidMount() {
  Ereignisse.auf(dieses.getEl(), 'scroll', dieses.aufScrollen, falsch);
  events.on(this.getEl(), 'Mausrad', NOOP, false);

  // Berechnen Sie den neuesten Knoten basierend auf dem Rendering let visibleCount = Math.ceil(this.getEl().offsetHeight / estimateItemSize);
  wenn (sichtbarerAnzahl === dieser.Zustand.sichtbarerAnzahl || sichtbarerAnzahl === 0) {
    zurückkehren;
  }
  // Aktualisiere endIndex, listHeight/offset, da visibleCount sich geändert hat this.updateState({ visibleCount, startIndex: this.state.startIndex });
}

getEl = () => {
    let el = dieser.Zustand.scrollRef || dieser.Zustand.Elemente;
    let parentEl: any = el.current?.parentElement;
    Schalter (Fenster.getComputedStyle(parentEl)?.overflowY) {
      Fall 'auto':
      Fall 'scrollen':
      Fall 'Overlay':
      Fall 'sichtbar':
        returniere übergeordnetesEl;
    }
    Dokumenttext zurückgeben;
};

beimScrollen = () => {
    requestAnimationFrame(() => {
      let { scrollTop } = this.getEl();
      let startIndex = binarySearch(positionen, scrollTop);

      // Da sich der Startindex ändert, aktualisiere den Endindex und die Listenhöhe/den Offset. this.updateState({ visibleCount: this.state.visibleCount, startIndex});
    });
  };

Als nächstes analysieren wir die wichtigsten Schritte. Beim Scrollen können wir den [scrollTop] des aktuellen virtuellen Listencontainers [scrollRef] abrufen. Über diese Distanz und [Positionen] (die alle Positionseigenschaften jedes Elements aufzeichnen) können wir den Startindex dieser Position abrufen. Um die Leistung zu verbessern, verwenden wir die binäre Suche:

// Tool-Funktion, in Tool-Datei einfügen export const binarySearch = (list: Array<PositionType>, value: number = 0) => {
  lass starten: Zahl = 0;
  let end: Zahl = Listenlänge – 1;
  lass tempIndex = null;
  während (Start <= Ende) {
    let midIndex = Math.floor((start + end) / 2);
    sei mittlererWert = Liste[mittlererIndex].unten;

    // Wenn die Werte gleich sind, wird der gefundene Knoten direkt zurückgegeben (da dieser unten liegt, sollte startIndex der nächste Knoten sein)
    wenn (Mittelwert === Wert) {
      gibt MidIndex + 1 zurück;
    }
    // Wenn der mittlere Wert kleiner als der Eingabewert ist, bedeutet das, dass der dem Wert entsprechende Knoten größer als Start ist, und Start wird um eine Position zurückbewegt, sonst wenn (Mittelwert < Wert) {
      Start = Mittelindex + 1;
    }
    // Wenn der mittlere Wert größer als der Eingabewert ist, bedeutet dies, dass der Wert vor dem mittleren Wert liegt und der Endknoten zur Mitte - 1 verschoben wird.
    sonst wenn (Mittelwert > Wert) {
      // tempIndex speichert alle Werte, die dem Wert am nächsten sind, wenn (tempIndex === null || tempIndex > midIndex) {
        tempIndex = mittlererIndex;
      }
      Ende = mittlererIndex - 1;
    }
  }
  gibt TempIndex zurück;
};

Sobald wir den Startindex erhalten haben, aktualisieren wir die Werte aller Eigenschaften im Komponentenstatus basierend auf dem Startindex.

 updateState = ({ sichtbareAnzahl, startIndex }) => {
    // Daten entsprechend dem neu berechneten Knoten aktualisieren this.setState({
      startOffset: startIndex >= 1 ? Positionen[startIndex - 1]?.unten : 0,
      Listenhöhe: getListHeight(Positionen),
      StartIndex,
      sichtbareAnzahl,
      endIndex: getEndIndex(diese.state.resources, startIndex, sichtbareAnzahl)
    });
  };

// Das Folgende ist eine Tool-Funktion, die in anderen Dateien abgelegt wird. export const getListHeight = (positions: Array<PositionType>) => {
    let index = Positionen.Länge - 1;
    Rückgabeindex < 0? 0: Positionen[Index].unten;
  };

exportiere const getEndIndex = (
  Ressourcen: Array<Daten>,
  startIndex: Zahl,
  sichtbareAnzahl: Zahl,
) => {
  let resourcesLength = Ressourcen.Länge;
  let endIndex = startIndex + sichtbareAnzahl;
  Gibt „Ressourcenlänge“ > 0 zurück? Math.min(Ressourcenlänge, Endindex) : Endindex;
}

4.4 Aktualisieren Sie die Artikelhöhen, wenn sie nicht gleich sind

An diesem Punkt haben wir das grundlegende DOM-Scrollen, die Datenaktualisierung und andere Logik abgeschlossen. Stellen Sie beim Testen jedoch fest, dass die Position und andere Vorgänge nicht aktualisiert wurden, wenn die Höhe nicht gleich ist? Wohin damit?
Hier sollte unser [componentDidUpdate] nützlich sein. Bei jedem Rendern des DOM sollten die Positions- und Höheninformationen des angezeigten Elements im Attribut [position] aktualisiert werden. Gleichzeitig müssen auch die aktuelle Gesamthöhe [istHeight] und der Offset [startOffset] aktualisiert werden.

 KomponenteDidUpdate() {
  dies.updateHeight();
}

  
updateHöhe = () => {
  let-Elemente: HTMLCollection = this.state.items.current?.children;
  wenn (!items.length) return;

  // Cache aktualisieren updateItemSize(positions, items);

  // Gesamthöhe aktualisieren let listHeight = getListHeight(positions);

  // Gesamten Offset aktualisieren let startOffset = getStartOffset(this.state.startIndex, positions);

  dies.setState({
    Listenhöhe,
    StartOffset,
  });
};

// Das Folgende ist eine Tool-Funktion, die in anderen Dateien abgelegt wird export const updateItemSize = (
  Positionen: Array<PositionType>,
  Elemente: HTMLCollection,
) => {
  Array.von(Elemente).fürJedes(Element => {
    let index = Zahl(item.getAttribute('Datenindex'));
    let { Höhe } = item.getBoundingClientRect();
    lass alteHeight = Positionen[Index].Höhe;

    //Wenn ein Unterschied besteht, aktualisiere alle Knoten nach diesem Knoten. let dValue = oldHeight - height;
    wenn (dWert) {
      Positionen[Index].unten = Positionen[Index].unten - dWert;
      Positionen[Index].Höhe = Höhe;

      für (let k = Index + 1; k < Positionen.Länge; k++) {
        Positionen[k].oben = Positionen[k - 1].unten;
        Positionen[k].unten = Positionen[k].unten - dWert;
      }
    }
  });
};

//Den aktuellen Offset abrufen export const getStartOffset = (
  startIndex: Zahl,
  Positionen: Array<PositionType> = [],
) => {
  startIndex >= 1 zurückgeben? Positionen[startIndex - 1]?.unten: 0;
};

export const getListHeight = (positions: Array<PositionType>) => {
  let index = Positionen.Länge - 1;
  Rückgabeindex < 0? 0: Positionen[Index].unten;
};

4.5 Externe Parameterdatenänderungen, Komponentendatenaktualisierungen

Wenn sich die von uns übergebene externe Datenquelle geändert hat, müssen wir in diesem letzten Schritt die Daten synchronisieren. Dieser Vorgang wird natürlich in der Methode getDerivedStateFromProps abgeschlossen.

 statisch getDerivedStateFromProps(
    nextProps: VirtualListProps,
    vorherigerZustand: VirtualListState,
  ) {
    const { Ressourcen, geschätzteArtikelgröße } = nextProps;
    wenn (Ressourcen !== vorherigerStatus.Ressourcen) {
      Positionen = initPositinoCache(geschätzteArtikelgröße, Ressourcen.Länge);

      // Höhe aktualisieren let listHeight = getListHeight(positions);

      // Gesamten Offset aktualisieren let startOffset = getStartOffset(prevState.startIndex, positions);

     
      let endIndex = getEndIndex(Ressourcen, vorherigerZustand.startIndex, vorherigerZustand.visibleCount);
     
      zurückkehren {
        Ressourcen,
        Listenhöhe,
        StartOffset,
        EndeIndex,
      };
    }
    gibt null zurück;
  }

5 Fazit

OK, eine vollständige virtuelle Listenkomponente ist fertig. Da die Renderfunktion jedes Datenelements angepasst ist, können Sie praktisch jedes Element scrollen, solange es in Listenform vorliegt. Natürlich kann nach den Informationen, die ich online gelesen habe, aufgrund von Netzwerkproblemen beim Scrollen der Bilder nicht die tatsächliche Höhe der Listenelemente garantiert werden, was zu Ungenauigkeiten führen kann. Darauf gehen wir hier vorerst nicht näher ein, Interessierte können gerne tiefer in die Materie einsteigen.

Dies ist das Ende dieses Artikels über die Implementierung der virtuellen React-Liste. Weitere relevante Inhalte zur virtuellen React-Liste finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • React implementiert eine hochadaptive virtuelle Liste

<<:  Grafisches Tutorial zur Installation und Konfiguration von MySQL 8.0.12 (Windows 10)

>>:  Zusammenfassung des Tutorials zur Installation und Bereitstellung von KVM für die VMware-Virtualisierung

Artikel empfehlen

Rendering-Funktion und JSX-Details

Inhaltsverzeichnis 1. Grundlagen 2. Knoten, Bäume...

Führt diese SQL-Schreibmethode wirklich dazu, dass der Index fehlschlägt?

Vorwort Im Internet gibt es häufig Artikel, die v...

Neue Verwendung von watch und watchEffect in Vue 3

Inhaltsverzeichnis 1. Neue Verwendung der Uhr 1.1...

So erstellen Sie eine monatliche Tabelle in einer gespeicherten MySQL-Prozedur

Lassen Sie uns, ohne ins Detail zu gehen, direkt ...

Weitergabe von Techniken für Farbkontrast und -harmonie im Web

Farbkontrast und Harmonie Unter kontrastierenden ...

Detaillierte Erklärung des JQuery-Selektors

Inhaltsverzeichnis Grundlegende Selektoren: Ebene...

So erstellen Sie Ihr eigenes Docker-Image und laden es auf Dockerhub hoch

1. Registrieren Sie zunächst Ihr eigenes Dockerhu...

Jenkins+Gitlab+Nginx Bereitstellung einer Front-End-Anwendung

Inhaltsverzeichnis Zugehörige Abhängigkeitsinstal...

Fallstudie zum JavaScript DOMContentLoaded-Ereignis

DOMContentLoaded-Ereignis Es wird buchstäblich au...

Detaillierte Erklärung des Hash-Jump-Prinzips von Vue

Inhaltsverzeichnis Der Unterschied zwischen Hash ...