Reagire codice "dopo il rendering"?


367

Ho un'app in cui devo impostare l'altezza di un elemento (diciamo "contenuto dell'app") in modo dinamico. Prende l'altezza del "chrome" dell'app e lo sottrae, quindi imposta l'altezza del "contenuto dell'app" per adattarlo al 100% all'interno di tali vincoli. Questo è semplicissimo con le viste vaniglia JS, jQuery o Backbone, ma sto lottando per capire quale sarebbe il giusto processo per farlo in React?

Di seguito è riportato un esempio di componente. Voglio essere in grado di impostare app-contentl'altezza in modo che sia al 100% della finestra meno la dimensione di ActionBare BalanceBar, ma come faccio a sapere quando viene eseguito il rendering di tutto e dove inserirò il calcolo in questa Classe di Reattività?

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;

4
All'inizio ero tipo "come farebbe Flexbox a risolvere questo problema?" poi ho ricordato la funzione di colonna in Flexbox e ha funzionato come un fascino!
Oscar Godson,

Risposte:


301

componentDidMount ()

Questo metodo viene chiamato una volta dopo il rendering del componente. Quindi il tuo codice sarebbe simile.

var AppBase = React.createClass({
  componentDidMount: function() {
    var $this = $(ReactDOM.findDOMNode(this));
    // set el height and width etc.
  },

  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
          <div className="inner-wrapper">
            <ActionBar title="Title Here" />
            <BalanceBar balance={balance} />
            <div className="app-content">
              <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

214
o componentDidUpdatese i valori possono cambiare dopo il primo rendering.
zackify

5
Sto cercando di cambiare una proprietà CSS impostata per la transizione, in modo che l'animazione inizi dopo il rendering. Sfortunatamente, la modifica del CSS in componentDidMount()non provoca una transizione.
eye_mew,

8
Grazie. Il nome è così intuitivo che mi chiedo perché stavo provando nomi ridicoli come "init" o addirittura "inizializzazione".
Pawel,

13
Modificarlo in componentDidMount è troppo veloce per il browser. Avvolgilo in un setTimeout e non concedergli alcun tempo effettivo. ovvero componentDidMount: () => { setTimeout(addClassFunction())}, o utilizzare rAF, la risposta di seguito fornisce questa risposta.

3
Questo sicuramente NON funziona. Se ottieni un elenco di nodi e quindi provi a scorrere l'elenco dei nodi, la lunghezza è uguale a 0. Fare setTimeout e aspettare 1 secondo ha funzionato per me. Sfortunatamente non sembra reagire ha un metodo che attende veramente fino a quando il DOM non viene reso.
NickJ

241

Uno svantaggio dell'uso componentDidUpdate, o componentDidMountè che sono effettivamente eseguiti prima che gli elementi dom siano stati disegnati, ma dopo che sono stati passati da React al DOM del browser.

Ad esempio, se è necessario impostare node.scrollHeight su node.scrollTop renderizzato, gli elementi DOM di React potrebbero non essere sufficienti. Devi aspettare che gli elementi siano stati verniciati per ottenere la loro altezza.

Soluzione:

Utilizzare requestAnimationFrameper assicurarsi che il codice venga eseguito dopo il disegno dell'oggetto appena renderizzato

scrollElement: function() {
  // Store a 'this' ref, and
  var _this = this;
  // wait for a paint before running scrollHeight dependent code.
  window.requestAnimationFrame(function() {
    var node = _this.getDOMNode();
    if (node !== undefined) {
      node.scrollTop = node.scrollHeight;
    }
  });
},
componentDidMount: function() {
  this.scrollElement();
},
// and or
componentDidUpdate: function() {
  this.scrollElement();
},
// and or
render: function() {
  this.scrollElement()
  return [...]

29
window.requestAnimationFrame non era abbastanza per me. Ho dovuto hackerarlo con window.setTimeout. Argggghhhhhh !!!!!
Alex,

2
Dispari. Forse è cambiato nella versione più recente di React, non penso che la chiamata a requestAnimationFrame sia necessaria. La documentazione dice: "Richiamato immediatamente dopo che gli aggiornamenti del componente sono stati scaricati sul DOM. Questo metodo non viene chiamato per il rendering iniziale. Utilizzare questo come opportunità per operare sul DOM quando il componente è stato aggiornato." ... vale a dire, è svuotato, il nodo DOM dovrebbe essere presente. - facebook.github.io/react/docs/…
Jim Soho,

1
@JimSoho, spero che tu abbia ragione nel risolvere questo problema, ma in realtà non c'è nulla di nuovo in quella documentazione. Questo vale per i casi limite in cui il dom in fase di aggiornamento non è sufficiente ed è importante attendere il ciclo di pittura. Ho provato a creare un violino con le nuove versioni e il vecchio, ma non sono riuscito a creare un componente abbastanza complesso per dimostrare il problema, anche andando indietro di alcune versioni ...
Graham P Heath

3
@neptunian A rigor di termini "[RAF] è chiamato [...] prima del prossimo ridipingere ..." - [ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]. In questa situazione il nodo deve ancora avere il suo layout calcolato dal DOM (noto anche come "reflowed"). Questo utilizza RAF come modo per saltare da prima del layout a dopo il layout. La documentazione del browser di Elm è un buon posto per altro: elmprogramming.com/virtual-dom.html#how-browsers-render-html
Graham P Heath

1
_this.getDOMNode is not a functionche diamine è questo codice?
OZZIE,

104

Nella mia esperienza window.requestAnimationFramenon è stato sufficiente per garantire che il DOM fosse stato completamente renderizzato / riflusso completo da componentDidMount. Ho un codice in esecuzione che accede al DOM immediatamente dopo una componentDidMountchiamata e l'utilizzo esclusivo window.requestAnimationFramecomporterebbe la presenza dell'elemento nel DOM; tuttavia, gli aggiornamenti alle dimensioni dell'elemento non vengono ancora riflessi poiché non è stato ancora eseguito un riflusso.

L'unico modo veramente affidabile per farlo funzionare era avvolgere il mio metodo in a setTimeoute a window.requestAnimationFrameper garantire che l'attuale stack di chiamate di React venisse cancellato prima di registrarsi per il rendering del frame successivo.

function onNextFrame(callback) {
    setTimeout(function () {
        requestAnimationFrame(callback)
    })
}

Se dovessi speculare sul motivo per cui ciò si sta verificando / necessario, potrei vedere gli aggiornamenti del DOM in batch di React e non applicare effettivamente le modifiche al DOM fino al completamento dello stack corrente.

In definitiva, se stai usando le misure DOM nel codice che stai attivando dopo i callback di React, probabilmente vorrai usare questo metodo.


1
Hai solo bisogno di setTimeout O di requestAnimationFrame, non di entrambi.

7
In genere, hai ragione. Tuttavia, nel contesto del metodo componentDidMount di React se si allega un requestAnimationFrame prima che lo stack sia terminato, il DOM potrebbe non essere effettivamente completamente aggiornato. Ho un codice che riproduce costantemente questo comportamento nel contesto dei callback di React. L'unico modo per essere sicuri che il tuo codice sia in esecuzione (ancora una volta, in questo caso d'uso specifico di React) dopo l'aggiornamento del DOM è lasciare che lo stack di chiamate venga eliminato per primo con un setTimeout.
Elliot Chong,

6
Noterai altri commenti sopra i quali menzionano la necessità di una soluzione alternativa, ad esempio: stackoverflow.com/questions/26556436/react-after-render-code/… Questo è l'unico metodo affidabile al 100% per questo caso d'uso React. Se dovessi avventurarmi in un'ipotesi, potrebbe essere dovuto agli stessi aggiornamenti di batch di React che potenzialmente non vengono applicati all'interno dello stack corrente (quindi rinviando la richiestaAnimationFrame al frame successivo per garantire che il batch venga applicato).
Elliot Chong,


2
Aspettare che l'attuale stack di chiamate venga cancellato non è ovviamente un modo "insensato" di parlare del loop degli eventi, ma a ciascuno il suo credo ...
Elliot Chong

17

Solo per aggiornare un po 'questa domanda con i nuovi metodi Hook, puoi semplicemente usare l' useEffecthook:

import React, { useEffect } from 'react'

export default function App(props) {

     useEffect(() => {
         // your post layout code (or 'effect') here.
         ...
     },
     // array of variables that can trigger an update if they change. Pass an
     // an empty array if you just want to run it once after component mounted. 
     [])
}

Inoltre, se si desidera eseguire prima della vernice del layout, utilizzare il useLayoutEffectgancio:

import React, { useLayoutEffect } from 'react'

export default function App(props) {

     useLayoutEffect(() => {
         // your pre layout code (or 'effect') here.
         ...
     }, [])
}

Secondo la documentazione di React, useLayoutEffect si verifica dopo che tutte le mutazioni del DOM hanno reagitojjorg
Philippe Hebert

È vero, ma viene eseguito prima che il layout abbia la possibilità di dipingere Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.che modificherò.
P Fuster,

Ti capita di sapere se useEffect viene eseguito dopo il reflow del browser (non quello che React chiama 'paint')? È sicuro richiedere scrollHeight di un elemento con useEffect?
eMontielG

È sicuro per l'uso Effetto
P Fuster

13

È possibile modificare lo stato e quindi eseguire i calcoli nel callback setState . Secondo la documentazione di React, questo è "garantito al fuoco dopo l'applicazione dell'aggiornamento".

Questo dovrebbe essere fatto dentro componentDidMounto altrove nel codice (come su un gestore di eventi di ridimensionamento) piuttosto che nel costruttore.

Questa è una buona alternativa window.requestAnimationFramee non presenta i problemi che alcuni utenti hanno menzionato qui (è necessario combinarli setTimeouto chiamarli più volte). Per esempio:

class AppBase extends React.Component {
    state = {
        showInProcess: false,
        size: null
    };

    componentDidMount() {
        this.setState({ showInProcess: true }, () => {
            this.setState({
                showInProcess: false,
                size: this.calculateSize()
            });
        });
    }

    render() {
        const appStyle = this.state.showInProcess ? { visibility: 'hidden' } : null;

        return (
            <div className="wrapper">
                ...
                <div className="app-content" style={appStyle}>
                    <List items={items} />
                </div>
                ...
            </div>
        );
    }
}

1
Questa è la mia risposta preferita Codice di reazione idiomatico pulito e buono.
Phatmann,

1
Questa è un'ottima risposta! Grazie!
Bryan Jyh Herng Chong

1
È bellissimo, vota questo commento!
bingeScripter

11

Sento che questa soluzione è sporca, ma qui andiamo:

componentDidMount() {
    this.componentDidUpdate()
}

componentDidUpdate() {
    // A whole lotta functions here, fired after every render.
}

Ora mi siedo qui e aspetto i voti negativi.


5
È necessario rispettare un ciclo di vita del componente React.
Túbal Martín,

2
@ TúbalMartín Lo so. Se hai un modo migliore per raggiungere lo stesso risultato, sentiti libero di condividere.
Jaakko Karhu,

8
Uhm, un +1 figurativo per "siediti qui e attendi i voti negativi". Uomo coraggioso. ; ^)
ruffin,

14
Piuttosto chiama un metodo da entrambi i cicli di vita, quindi non devi innescare cicli da altri cicli.
Tjorriemorrie,

1
componentWillReceiveProps dovrebbe fare questo
Pablo

8

React ha pochi metodi del ciclo di vita che aiutano in queste situazioni, gli elenchi inclusi ma non limitati a getInitialState, getDefaultProps, componentWillMount, componentDidMount ecc.

Nel tuo caso e nei casi che devono interagire con gli elementi DOM, devi attendere che il dom sia pronto, quindi usa componentDidMount come di seguito:

/** @jsx React.DOM */
var List = require('../list');
var ActionBar = require('../action-bar');
var BalanceBar = require('../balance-bar');
var Sidebar = require('../sidebar');
var AppBase = React.createClass({
  componentDidMount: function() {
    ReactDOM.findDOMNode(this).height = /* whatever HEIGHT */;
  },
  render: function () {
    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar title="Title Here" />
          <BalanceBar balance={balance} />
          <div className="app-content">
            <List items={items} />
          </div>
        </div>
      </div>
    );
  }
});

module.exports = AppBase;

Inoltre, per ulteriori informazioni sul ciclo di vita di React, è possibile consultare il link seguente: https://facebook.github.io/react/docs/state-and-lifecycle.html

getInitialState, getDefaultProps, componentWillMount, componentDidMount


il mio componente ha eseguito il mount delle esecuzioni prima che il rendering della pagina causasse un grande ritardo quando una chiamata API carica i dati.
Jason G,

6

Ho riscontrato lo stesso problema.

Nella maggior parte degli scenari utilizzando l'hack-ish setTimeout(() => { }, 0)in componentDidMount()funzionato.

Ma non in un caso speciale; e non volevo usare il ReachDOM findDOMNodepoiché la documentazione dice:

Nota: findDOMNode è un tratteggio di escape utilizzato per accedere al nodo DOM sottostante. Nella maggior parte dei casi, l'uso di questo tratteggio di escape è scoraggiato perché perfora l'astrazione del componente.

(Fonte: findDOMNode )

Quindi in quel particolare componente ho dovuto usare l' componentDidUpdate()evento, quindi il mio codice ha finito per essere così:

componentDidMount() {
    // feel this a little hacky? check this: http://stackoverflow.com/questions/26556436/react-after-render-code
    setTimeout(() => {
       window.addEventListener("resize", this.updateDimensions.bind(this));
       this.updateDimensions();
    }, 0);
}

E poi:

componentDidUpdate() {
    this.updateDimensions();
}

Infine, nel mio caso, ho dovuto rimuovere l'ascoltatore creato in componentDidMount:

componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions.bind(this));
}

2

Dopo il rendering, è possibile specificare l'altezza come di seguito e specificare l'altezza per i componenti di reazione corrispondenti.

render: function () {
    var style1 = {height: '100px'};
    var style2 = { height: '100px'};

   //window. height actually will get the height of the window.
   var hght = $(window).height();
   var style3 = {hght - (style1 + style2)} ;

    return (
      <div className="wrapper">
        <Sidebar />
        <div className="inner-wrapper">
          <ActionBar style={style1} title="Title Here" />
          <BalanceBar style={style2} balance={balance} />
          <div className="app-content" style={style3}>
            <List items={items} />
          </div>
        </div>
      </div>
    );`
  }

oppure puoi specificare l'altezza di ciascun componente di reazione usando sass. Specificare i primi 2 div principali del componente di reazione con larghezza fissa e poi l'altezza del div principale del terzo componente con auto. Quindi in base al contenuto del terzo div verrà assegnata l'altezza.


2

In realtà sto riscontrando problemi con un comportamento simile, eseguo il rendering di un elemento video in un componente con l'attributo id, quindi quando RenderDOM.render () termina carica un plug-in che ha bisogno dell'id per trovare il segnaposto e non riesce a trovarlo.

SetTimeout con 0ms all'interno del componentDidMount () risolto :)

componentDidMount() {
    if (this.props.onDidMount instanceof Function) {
        setTimeout(() => {
            this.props.onDidMount();
        }, 0);
    }
}

1

Dalla documentazione di ReactDOM.render () :

Se viene fornito il callback opzionale, verrà eseguito dopo il rendering o l'aggiornamento del componente.


9
puoi aggiungere un esempio di come usarlo? Per lo più restituisco elementi dal metodo render, non chiamo render e fornisco valori.
dcsan,

23
Sfortunatamente il callback che menzioni è disponibile solo per il livello superioreReactDOM.render , non per il livello del componenteReactElement.render (che è l'argomento qui).
Bramus,

1
L'esempio qui sarebbe utile
DanV

1
Ho cliccato sul link nella tua risposta e non sono riuscito a trovare la riga che hai citato e la tua risposta non include informazioni sufficienti per lavorare senza di essa. Consulta stackoverflow.com/help/how-to-answer per consigli su come scrivere una buona domanda
Benubird,

1

Per me, nessuna combinazione di window.requestAnimationFrameo setTimeoutprodotto risultati coerenti. A volte ha funzionato, ma non sempre - o a volte sarebbe troppo tardi.

L'ho risolto ripetendo window.requestAnimationFrametutte le volte necessarie.
(Tipicamente 0 o 2-3 volte)

La chiave è diff > 0: qui possiamo assicurarci esattamente quando la pagina si aggiorna.

// Ensure new image was loaded before scrolling
if (oldH > 0 && images.length > prevState.images.length) {
    (function scroll() {
        const newH = ref.scrollHeight;
        const diff = newH - oldH;

        if (diff > 0) {
            const newPos = top + diff;
            window.scrollTo(0, newPos);
        } else {
            window.requestAnimationFrame(scroll);
        }
    }());
}

0

Ho avuto una situazione strana quando ho bisogno di stampare un componente di reazione che riceve una grande quantità di dati e dipinge su tela. Ho provato tutti gli approcci citati, nessuno di loro ha funzionato in modo affidabile per me, con requestAnimationFrame all'interno di setTimeout ottengo tela vuota nel 20% delle volte, quindi ho fatto quanto segue:

nRequest = n => range(0,n).reduce(
(acc,val) => () => requestAnimationFrame(acc), () => requestAnimationFrame(this.save)
);

Fondamentalmente ho creato una catena di requestAnimationFrame's, non sono sicuro che questa sia una buona idea o no, ma finora funziona nel 100% dei casi (sto usando 30 come valore per n variabile).


0

Esiste in realtà una versione molto più semplice e più pulita rispetto all'utilizzo di animazioni di richiesta o timeout. Iam ha sorpreso che nessuno lo abbia sollevato: il gestore di onload di vanilla-js. Se è possibile, utilizzare il componente ha fatto il mount, in caso contrario, è sufficiente associare una funzione sul gancio di caricamento del componente jsx. Se si desidera che la funzione esegua ogni rendering, eseguirla anche prima di restituire si ottiene la funzione di rendering. il codice sarebbe simile al seguente:

runAfterRender = () => 
{
  const myElem = document.getElementById("myElem")
  if(myElem)
  {
    //do important stuff
  }
}

render()
{
  this.runAfterRender()
  return (
    <div
      onLoad = {this.runAfterRender}
    >
      //more stuff
    </div>
  )
}

}


-1

Un po 'di aggiornamento con le ES6classi anzichéReact.createClass

import React, { Component } from 'react';

class SomeComponent extends Component {
  constructor(props) {
    super(props);
    // this code might be called when there is no element avaliable in `document` yet (eg. initial render)
  }

  componentDidMount() {
    // this code will be always called when component is mounted in browser DOM ('after render')
  }

  render() {
    return (
      <div className="component">
        Some Content
      </div>
    );
  }
}

Inoltre, controlla i metodi del ciclo di vita dei componenti React: Il ciclo di vita dei componenti

Ogni componente ha molti metodi simili ad componentDidMountes.

  • componentWillUnmount() - il componente sta per essere rimosso dal DOM del browser

1
Nessuna mancanza di rispetto, ma come risponde alla domanda? Mostrare un aggiornamento su ES6 non è realmente correlato alla domanda / non cambia nulla. Tutte le risposte molto più vecchie parlano già di come componentDidMount non funziona da solo.
dave4jr,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.