2016-06-17 22 views
6

Ich weiß, das ist nicht idiomatische Reagieren, aber ich arbeite an einer React-Komponente (eine inverse Scrollview), die Downstream virtuellen DOM Änderungen benachrichtigt werden müssen, bevor sie gerendert werden und nachdem sie gerendert werden.Sprudelnde KomponenteWillUpdate und KomponenteDidUpdate

Dies ist ein wenig schwer zu erklären, so machte ich einen JSFiddle mit einem Startpunkt: https://jsfiddle.net/7hwtxdap/2/

Mein Ziel ist es, wie das Protokoll Blick zu haben:

Begin log 

render 

rendered small 

rendered small 

rendered small 

before update 

rendered big 

after update 

before update 

rendered big 

after update 

before update 

rendered big 

after update 

mir bewusst bin, die manchmal Reagieren Batch-DOM-Änderungen und das ist in Ordnung (dh die Log-Ereignisse könnten before update; rendered big; rendered big; after update; ... sein) - wichtiger Teil ist, dass ich tatsächlich vor und nach dem DOM Änderungen benachrichtigt werden.


ich das Verhalten durch Angabe Rückrufe manuell annähern kann, wie hier geschehen: https://jsfiddle.net/7hwtxdap/4/. Dieser Ansatz ist jedoch nicht skalierbar. Ich brauche zum Beispiel Ereignisse, die von Nachkommen statt von Kindern sprudeln. und würde diese Art von Event-Handlern lieber nicht zu jeder Komponente hinzufügen, die ich verwende.


Anwendungsfall:

ich auf sie ein „inverted-Scrolling-Komponente“ für Nachrichten gerade arbeite (wo, wenn neue Elemente hinzugefügt oder bestehende Elemente Größe ändern, ändern die Grundlage für die Rolle aus der ist unten im Inhalt und nicht oben in jeder Messaging-App), die Kinder dynamisch verändert hat (ähnlich der Klasse <MyElement />, aber mit variabler Höhe, Daten von Ajax usw.). Diese erhalten keine Daten von einem Flux-ähnlichen Zustandsspeicher, sondern verwenden stattdessen ein Pub-Sub-Datensynchronisationsmodell (das ungefähr mit dem setTimeout() angenähert ist).

Um inverse Scrollen funktioniert, müssen Sie so etwas wie dies tun:

anyChildrenOfComponentWillUpdate() { 
    let m = this.refs.x; 
    this.scrollBottom = m.scrollHeight - m.scrollTop - m.offsetHeight; 
} 
anyChildrenOfComponentDidUpdate() { 
    let m = this.refs.x; 
    m.scrollTop = m.scrollHeight - m.offsetHeight - this.scrollBottom; 
} 

Wichtig ist zu diesem Entwurf würde ich das Element in der Lage sein mögen, um andere Elemente zu wickeln, die nicht „scrolling invertiert aware "(dh sie müssen keinen speziellen Code haben, um den invertierten Scroll-Handler zu benachrichtigen, dass sie sich geändert haben).

+0

Was componentWillMount und componentDidUpdate Lifecycle-Methoden? – Pcriulan

+0

Die "sprudeln" auch nicht an die Eltern. –

+0

Wie verwalten Sie Staat? Verwenden Sie Redux oder ähnliches? Auch, warum genau müssen Sie wissen, wenn Kinder aktualisieren? Es könnte hilfreich sein, wenn Sie erklären, was Sie mit dieser Information machen. Es könnte einen anderen Weg geben, damit umzugehen. – jaybee

Antwort

1

Es ist schwer zu wissen, was die beste Lösung für Ihr Problem ist, ohne zu wissen, warum Sie diese Funktionalität benötigen. Ich werde jedoch auf diesen speziellen Punkt eingehen:

Ich brauche zum Beispiel, um Ereignisse von Nachkommen eher als Kinder sprudeln zu lassen; und ich würde diese Art von Event-Handlern lieber nicht zu jeder Komponente hinzufügen müssen, die ich verwende.

Ich würde zwei Änderungen an Ihrer Lösung vorschlagen. Die erste ist die Verwendung von context, aber lesen Sie die Warnungen, bevor Sie sich dazu entschließen. Auf diese Weise können Sie Ihre Rückrufe in einer einzigen Komponente definieren und sofort in allen Nachkommen verfügbar machen.

Die zweite Änderung wäre, alle Logging-Logik in eine separate Komponente zu verschieben, und andere Komponenten erben es bei Bedarf. Diese Komponente würde in etwa so aussehen:

class LoggingComponent extends React.Component { 
    componentWillUpdate() { 
    if (this.context.onWillUpdate) { 
     this.context.onWillUpdate(); 
    } 
    } 
    componentDidUpdate() { 
    if (this.context.onDidUpdate) { 
     this.context.onDidUpdate(); 
    } 
    } 
} 

LoggingComponent.contextTypes = { 
    onWillUpdate: React.PropTypes.func, 
    onDidUpdate: React.PropTypes.func 
}; 

Auf diese Weise können Sie leicht diese Funktionalität, um neue Komponenten hinzuzufügen, ohne die Logik zu duplizieren. Ich habe Ihre jsfiddle mit einer funktionierenden Demo aktualisiert.

+0

Danke James. Es kann sein, dass die Verwendung von Kontext mit der erweiterten Klasse der sauberste Weg ist - obwohl das Erzwingen, alle nachgeordneten Klassen von einer bestimmten Klasse zu erben, immer noch nicht groß ist. Ich habe auch die Frage oben mit etwas spezifischerem Kontext aktualisiert, um zu sehen, ob das hilft. –

3

Reagieren, von Entwurf, respektiert, dass ein setState Aufruf auf einer Komponente ein Neu-Rendering von that Komponente auslösen sollte. Die zuverlässigste Art, auf den Lebenszyklus einer Komponente zuzugreifen, ist die Komponente selbst.

Wie wäre es mit Ihnen eine notifier() Rückruf als Requisite und rufen Sie dann diese Funktion innerhalb der componentWillMount und componentDidUpdate Lebenszyklus Haken der Kinder-Komponenten?

Here's a working js-fiddle mit dieser Idee. Lass mich wissen was kommt.

Edit 1

Wenn Sie lieber nicht Rückrufe hin und her durch jedes einzelne Kind Komponente zu übergeben, dann würde ich sagen, Sie haben gerade über einen bestimmten Anwendungsfall von Redux und Flux kommen.

Mit Redux kann eine Komponente über eine Reducer-Funktion den Status eines Speichers ändern. Jede Komponente, die in diesem Geschäft nach einer Änderung sucht, wird dann erneut gerendert.Flux verwendet die gleiche Idee.

Also ich würde vorschlagen, dass Sie redux verwenden, um Zustandseinstellung zu handhaben. Um zu beginnen, könntest duvon Reducs Schöpfer Dan Abramov durchgehen.

+0

Hallo Cent, Sie können sehen, dass ich in der zweiten JSFiddle tatsächlich so etwas getan habe - versuchen Callbacks überall zu vermeiden! –

+0

Sie haben Recht @AaronYodaiken Ich habe gerade meine Antwort bearbeitet, um Ihre spezifischen Präferenzen zu übernehmen. Redux/Flux ist der Weg zu gehen, mein Freund :) – Cent

+0

Cent, Redux ist großartig, aber hier ist es nicht die richtige Lösung - meine Scroll-Container sollte nicht wissen, in welchen Pfaden die Dinge drin sind (diese bricht Einkapselung!) –

0

Vielleicht können Sie einen Blick auf react-virtualized werfen, die ein Beispiel für die umgekehrte Bildlaufliste hat.

export default class Example extends Component { 
    constructor (props) { 
    super(props) 

    this.state = { 
     list: [] 
    } 
    } 

    componentDidMount() { 
    this._interval = setInterval(::this._updateFeed, 500) 
    } 

    componentWillUnmount() { 
    clearInterval(this._interval) 
    } 

    render() { 
    const { list } = this.state 

    return (
     <div className={styles.VirtualScrollExample}> 
     <VirtualScroll 
      ref='VirtualScroll' 
      className={styles.VirtualScroll} 
      width={300} 
      height={200} 
      rowHeight={60} 
      rowCount={list.length} 
      rowRenderer={::this._rowRenderer} 
     /> 
     </div> 
    ) 
    } 

    _updateFeed() { 
    const list = [ ...this.state.list ] 

    list.unshift(
     // Add new item here 
    ) 

    this.setState({ list }) 

    // If you want to scroll to the top you can do it like this 
    this.refs.VirtualScroll.scrollToRow(0) 
    } 

    _rowRenderer (index) { 
    return (
     // Your markup goes here 
    ) 
    } 
} 
+0

Was reaktionsvirtualisiert tut, beruht auf einer statischen 'rowHeight'; Das bedeutet, dass es nicht auf DOM-Änderungen warten muss. –

1

Wie James sagte: Sie Kontext verwenden, um eine Callback-Funktion zu definieren, welche Sie unten auf alle Nachkommen Komponenten passieren können, und wenn Sie nicht Ihre Komponenten erweitern möchten können Sie einen Dekorateur verwenden, um Wickeln Sie eine Komponente höherer Ordnung um sie herum. Etwas wie folgt aus:

In Ihrem Container:

class Container extends React.Component { 
    static childContextTypes = { 
     log: React.PropTypes.func.isRequired 
    }; 

    getChildContext() { 
     return { 
      log: (msg) => { 
       console.log(msg) 
      } 
     } 
    } 

    render() { 
    console.log("Begin Log") 
    return (
     <div> 
      <MyElement /> 
      <MyElement /> 
      <MyElement /> 
     </div> 
    ); 
    } 
} 

Der Dekorateur Klasse:

export const Logger = ComposedComponent => class extends React.Component { 
    static contextTypes = { 
     log: React.PropTypes.func.isRequired 
    }; 

    constructor(props) { 
     super(props) 
    } 

    componentWillReceiveProps(nextProps) { 
     this.context.log('will receive props'); 
    } 

    componentWillUpdate() { 
     this.context.log('before update'); 
    } 

    componentDidUpdate() { 
     this.context.log('after update'); 
    } 

    render() { 
     return <ComposedComponent {...this.props}/> 
    } 
} 

dann die verschiedenen Komponenten dekorieren Sie

@Logger 
class MyElement extends React.Component { 
... 
} 
+0

Hallo ich glaube nicht, dass dies funktioniert, da die 'ComposedComponent' ist, was die Update-Ereignisse erhalten, nicht die Wrapped/Decorator-Komponente. –