Rendern Reagieren Sie Komponenten mit Versprechungen innerhalb der Rendermethode

73

Ich habe eine Komponente, die eine Sammlung von Elementen als Requisiten erhält und mapsie zu einer Sammlung von Komponenten zusammenfügt, die als untergeordnete Elemente einer übergeordneten Komponente gerendert werden. Wir verwenden Bilder, die WebSQLals Byte-Arrays gespeichert sind . Innerhalb der mapFunktion erhalte ich eine Bild-ID vom Element und rufe das asynchron an DAL, um das Byte-Array für das Bild zu erhalten. Mein Problem ist, dass ich das Versprechen nicht an React weitergeben kann, da es nicht für Versprechen beim Rendern ausgelegt war (soweit ich das sowieso nicht beurteilen kann). Ich komme aus einem C#Hintergrund, also suche ich awaitwahrscheinlich nach dem Schlüsselwort für die Neusynchronisierung von verzweigtem Code.

Die mapFunktion sieht ungefähr so ​​aus (vereinfacht):

var items = this.props.items.map(function (item) {
        var imageSrc = Utils.getImageUrlById(item.get('ImageId')); // <-- this contains an async call
        return (
            <MenuItem text={item.get('ItemTitle')}
                      imageUrl={imageSrc} />
       );
    });

und die getImageUrlByIdMethode sieht so aus:

getImageUrlById(imageId) {
    return ImageStore.getImageById(imageId).then(function (imageObject) { //<-- getImageById returns a promise
       var completeUrl = getLocalImageUrl(imageObject.StandardConImage);
       return completeUrl;
    });
}

Dies funktioniert nicht, aber ich weiß nicht, was ich ändern muss, damit dies funktioniert. Ich habe versucht, der Kette ein weiteres Versprechen hinzuzufügen, aber dann wird eine Fehlermeldung angezeigt, da meine Renderfunktion ein Versprechen anstelle von legalem JSX zurückgibt. Ich dachte, dass ich vielleicht eine der ReactLebenszyklusmethoden nutzen muss, um die Daten abzurufen, aber da ich die propsbereits dort haben muss, kann ich nicht herausfinden, wo ich das tun kann.

Elad Lachmi
quelle

Antworten:

88

render()Die Methode sollte die Benutzeroberfläche von rendern this.propsund this.statezum asynchronen Laden von Daten können Sie die Zuordnung this.statespeichern imageId: imageUrl.

Dann in Ihrer componentDidMount()Methode können Sie bevölkern imageUrlaus imageId. Dann sollte die render()Methode durch Rendern des this.stateObjekts schlicht und einfach sein

Beachten Sie, dass das this.state.imageUrlsasynchron gefüllt wird, sodass das gerenderte Bildlistenelement nach dem Abrufen seiner URL einzeln angezeigt wird. Sie können das auch this.state.imageUrlsmit allen Bild-IDs oder -Indexen (ohne URLs) initialisieren. Auf diese Weise können Sie beim Laden dieses Bildes einen Loader anzeigen.

constructor(props) {
    super(props)
    this.state = {
        imageUrls: []
    };
}

componentDidMount() {
    this.props.items.map((item) => {
        ImageStore.getImageById(item.imageId).then(image => {
            const mapping = {id: item.imageId, url: image.url};
            const newUrls = this.state.imageUrls.slice();
            newUrls.push(mapping);

            this.setState({ imageUrls: newUrls });
        })
    });
}

render() {
    return (
      <div>
        {this.state.imageUrls.map(mapping => (
            <div>id: {mapping.id}, url: {mapping.url}</div>
        ))}
      </div>
    );
}
Wint
quelle
5
Vielen Dank! Das hat funktioniert. Das einzige, was ich hier ändern würde (zumindest für meinen Gebrauch), ist dies zu tun, componentWillMountanstatt componentDidMountkein anfängliches "leeres" Rendern zu haben. componentWillMountwird vor dem ersten Rendern ausgeführt.
Elad Lachmi
2
@Wint In dieser Zeile ist ein Fehler aufgetreten: var newUrls = self.state.imageUrls.slice().push(mapping)Der zurückgegebene Wert ist keine flache Kopie des Arrays, sondern die Länge des neuen Arrays. Siehe Array.prototype.push ()
jherax
1
@Wint, sehr neu zu reagieren, bitte verzeihen Sie mein Unverständnis, aber ich habe ein paar Fragen: (1) Dies scheint eine Performance Land Mine zu sein. Reagieren Sie für eine spätere Iteration auf Timer oder Caching, um die Zeit zu verkürzen, in der das Array geklont wird? (In Ember müssen wir die Run-Schleife planen, nicht sicher, ob es in React ein Äquivalent gibt.) (2) Dies scheint etwas in der Reihenfolge zurückzugeben, in der die asynchronen Vorgänge abgeschlossen sind. Empfehlen Sie Strategien zur Aufrechterhaltung der Ordnung?
Thomas
28

Oder Sie können Reaktionsversprechen verwenden:

Installieren Sie das Paket:

npm i react-promise

Und Ihr Code sieht ungefähr so ​​aus:

import Async from 'react-promise'

var items = this.props.items.map(function (item) {
    var imageSrc = Utils.getImageUrlById(item.get('ImageId')); // <-- this contains an async call
    return (
        <Async promise={imageSrc} then={(val) => <MenuItem text={item.get('ItemTitle')} imageUrl={val}/>} />    
    );
});

Bearbeiten: Okt 2019

Der letzte Build von React-Promise bietet einen Hook namens usePromise:

import usePromise from 'react-promise';

const ExampleWithAsync = (props) => {
  const {value, loading} = usePromise<string>(prom)
  if (loading) return null
  return <div>{value}</div>}
}

Vollständige Dokumentation: Reaktionsversprechen

ZEE
quelle