2016-06-17 37 views
6

So che non si tratta di un Reazione idiomatica, ma sto lavorando a un componente React (una scrollview inversa) che deve essere notificato delle modifiche DOM virtuali a valle sia prima che vengano renderizzate sia dopo il rendering.Componente di gorgogliamentoWillUpdate e componentDidUpdate

Questo è un po 'difficile da spiegare così ho fatto un JSFiddle con un punto di partenza: https://jsfiddle.net/7hwtxdap/2/

Il mio obiettivo è quello di avere l'aspetto di registro come:

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 

Mi rendo conto che a volte React batch DOM cambia e va bene (cioè gli eventi del registro potrebbero essere before update; rendered big; rendered big; after update; ...) - la parte importante è che mi viene effettivamente notificato prima e dopo le modifiche al DOM.


posso approssimare manualmente il comportamento callback specificando, come fatto qui: https://jsfiddle.net/7hwtxdap/4/. Tuttavia, questo approccio non scala - ho bisogno, ad esempio, di far scoppiare gli eventi dai discendenti piuttosto che dai bambini; e preferisco non dover aggiungere questo tipo di gestori di eventi a ogni componente che uso.


Caso d'uso:

sto lavorando per rendere un "componente scrolling invertito" per i messaggi (dove, quando vengono aggiunti nuovi elementi o elementi esistenti modificare le dimensioni, la base per il cambiamento di scorrimento è dal parte inferiore del contenuto piuttosto che l'ala superiore di ogni app di messaggistica) che ha bambini modificati dinamicamente (simile alla classe <MyElement /> ma con altezza variabile, dati da ajax, ecc.). Questi dati non ricevono dati da un archivio di stato simile a Flux ma piuttosto utilizzano un modello di sincronizzazione dei dati pub-sub (abbastanza approssimativo con setTimeout()).

Per far funzionare lo scorrimento inverso, è necessario fare qualcosa di simile:

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; 
} 

È importante sottolineare che a questo progetto, mi piacerebbe essere in grado di avvolgere l'elemento attorno altri elementi che non sono "invertiti scorrimento consapevole "(cioè non è necessario avere un codice speciale per notificare al gestore di scorrimento invertito che sono stati modificati).

+0

Che dire componentWillMount e componentDidUpdate metodi del ciclo di vita? – Pcriulan

+0

Anche quelli non "fanno bolle" con i genitori. –

+0

Come stai gestendo lo stato? Stai usando redux o qualcosa del genere? Inoltre, perché è esattamente necessario sapere quando i bambini si aggiornano? Potrebbe aiutarti se hai spiegato cosa farai con quelle informazioni. Potrebbe esserci un modo diverso di gestirlo. – jaybee

risposta

1

È difficile sapere quale sia la soluzione migliore per il tuo problema senza sapere perché hai bisogno di questa funzionalità. Tuttavia, affronterò questo specifico punto:

Ho bisogno, ad esempio, di far scoppiare gli eventi dai discendenti piuttosto che dai bambini; e preferirei non dover aggiungere questo tipo di gestori di eventi a ogni componente che utilizzo.

Suggerirei due modifiche alla soluzione per questo. Il primo è considerare l'utilizzo di context, ma assicurati di leggere gli avvertimenti prima di decidere di farlo. Ciò ti consentirà di definire le tue callback in un singolo componente e renderle immediatamente disponibili in tutti i discendenti.

La seconda modifica sarebbe spostare tutta la logica di registrazione in un componente separato e avere altri componenti ereditarlo in base alle esigenze. Questo componente sarebbe simile:

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 
}; 

In questo modo, si può facilmente aggiungere questa funzionalità a nuovi componenti senza duplicare la logica. Ho aggiornato il tuo jsfiddle con una demo funzionante.

+0

Grazie James. Può essere che usare il contesto con la classe estesa sia il modo più pulito per farlo, anche se forzare tutte le classi downstream ad ereditare da una particolare classe non è ancora grandioso. Ho anche aggiornato la domanda di cui sopra con un contesto più specifico per vedere se questo aiuta. –

3

Reagire, in base alla progettazione, rispetta che una chiamata setState su un componente deve attivare un re-rendering del componente that. Il modo più affidabile che conosca per agganciare il ciclo di vita di un componente è sul componente stesso.

Che ne dici di passare una chiamata notifier() come supporto e quindi chiamare questa funzione all'interno del ciclo di vita componentWillMount e componentDidUpdate dei componenti figli?

Here's a working js-fiddle con questa idea. Fammi sapere cosa succede.

Modifica 1

Se si preferisce non passa callback avanti e indietro attraverso ogni componente singolo bambino, allora direi che hai appena incontrato un particolare caso d'uso di Redux e Flux.

Con Redux, un componente può, attraverso una funzione di riduzione, modificare lo stato di un negozio. Ogni componente che guarda per un cambiamento in quel negozio viene quindi ri-reso.Flux utilizza la stessa idea.

Quindi suggerirei di utilizzare Redux per gestire le impostazioni dello stato. Per iniziare, si può passare attraverso these tutorial videos dal creatore di Redux, Dan Abramov.

+0

Hi Cent, puoi vedere che ho effettivamente fatto qualcosa del genere nel secondo JSFiddle - cercando di evitare di far passare i callback ovunque! –

+0

Hai ragione @AaronYodaiken Ho appena modificato la mia risposta per accettare le tue preferenze specifiche. Redux/Flux è la strada da percorrere quindi, amico mio :) – Cent

+0

Cent, il redux è ottimo ma qui non è proprio la soluzione giusta - il mio contenitore di scroll non dovrebbe aver bisogno di sapere a quali percorsi le cose al suo interno dipendono (questo rompe l'incapsulamento!) –

0

Forse si può dare un'occhiata a react-virtualized che ha un esempio per la lista di scorrimento inverso.

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

Ciò che sta facendo reagire-virtualizzato è affidarsi a un 'rowHeight' statico; il che significa che non ha bisogno di ascoltare le modifiche del DOM. –

1

Come James ha detto, è possibile utilizzare contesto per definire una funzione di callback che si può tramandare a tutti i componenti discendenti e se non si desidera estendere i componenti è possibile utilizzare un decoratore per avvolgere un componente di ordine superiore attorno a loro. Qualcosa di simile a questo:

In contenitore:

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> 
    ); 
    } 
} 

La classe decoratrice:

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}/> 
    } 
} 

poi decorare i componenti che si desidera

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

Salve, non penso che funzionerà, dal momento che 'ComposedComponent' è ciò che riceve gli eventi di aggiornamento, non il componente wrapper/decoratore. –