"İşlemden sonra" koduna tepki gösterilsin mi?


367

Dinamik olarak bir öğenin yüksekliğini ("app-content" diyelim) ayarlamanız gereken bir uygulama var. Uygulamanın "krom" yüksekliğini alır ve çıkarır ve sonra "uygulama içeriğinin" yüksekliğini bu kısıtlamalar dahilinde% 100 sığacak şekilde ayarlar. Bu vanilya JS, jQuery veya Omurga görünümleri ile süper basit, ama React bunu yapmak için doğru sürecin ne olacağını bulmak için mücadele ediyorum?

Aşağıda örnek bir bileşen verilmiştir. Ben sete isterler app-content100 pencerenin% eksi büyüklüğünde olmak yüksekliği 'ın ActionBarve BalanceBar, ama nasıl her şey işlendiğinde anlarım ve nerede Class tepki bu hesaplama bişeyler koyardı?

/** @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
İlk başta "flexbox bunu nasıl düzeltir?" sonra Flexbox sütun özelliği hatırladı ve bir cazibe gibi çalıştı!
Oscar Godson

Yanıtlar:


301

componentDidMount ()

Bu yöntem, bileşeniniz oluşturulduktan sonra bir kez çağrılır. Yani kodunuz böyle görünecektir.

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
veya componentDidUpdateilk render işleminden sonra değerler değişebilirse.
zackify

5
Animasyon oluşturduktan sonra başlayacak şekilde geçişe ayarlanmış bir css özelliğini değiştirmeye çalışıyorum. Ne yazık ki, css'yi değiştirmek componentDidMount()geçişe neden olmaz.
eye_mew

8
Teşekkürler. İsim o kadar sezgisel ki neden "init", hatta "başlatıcı" gibi saçma isimler denediğimi merak ediyorum.
Pawel

13
ComponentDidMount'ta değiştirmek tarayıcı için çok hızlı. Bir setTimeout'a sarın ve gerçek zaman vermeyin. yani componentDidMount: () => { setTimeout(addClassFunction())}veya rAF kullanın, bunun altındaki cevap bu cevabı verir.

3
Bu kesinlikle işe yaramaz. Bir düğüm listesi alıp düğüm listesi üzerinde yineleme yapmaya çalışırsanız, uzunluğun 0'a eşit olduğunu görürsünüz. SetTimeout yapmak ve 1 saniye beklemek benim için çalıştı. Ne yazık ki tepki DOM oluşturulduktan sonra gerçekten bekleyen bir yöntem var gibi görünmüyor.
NickJ

241

Kullanmanın bir dezavantajı componentDidUpdate, veya componentDidMountdom öğeleri çizilmeden önce, ancak React'ten tarayıcının DOM'sine geçtikten sonra yürütülmeleri.

Örneğin, node.scrollHeight öğesini işlenen node.scrollTop olarak ayarlamanız gerekiyorsa, React'in DOM öğeleri yeterli olmayabilir. Yüksekliklerini elde etmek için elemanların boyanmasını beklemeniz gerekir.

Çözüm:

requestAnimationFrameKodunuzun yeni oluşturulan nesnenizin resminden sonra çalışmasını sağlamak için kullanın

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 benim için yeterli değildi. Ben window.setTimeout ile kesmek zorunda kaldı. Argggghhhhhh !!!!!
Alex

2
Garip. Belki de React'in en son sürümünde değişti, requestAnimationFrame çağrısının gerekli olduğunu düşünmüyorum. Belgelerde: "Bileşenin güncelleştirmeleri DOM'da temizlendikten hemen sonra çağrılır. Bu yöntem ilk oluşturma için çağrılmaz. Bileşen güncellendiğinde bunu DOM'da çalışma fırsatı olarak kullanın." ... yani, temizlenir, DOM düğümü mevcut olmalıdır. - facebook.github.io/react/docs/…
Jim Soho

1
@JimSoho, umarım bunun düzeltildiğini haklı çıkarırsınız, ancak aslında bu dokümantasyonda yeni bir şey yoktur. Bu, güncellenen dom'un yeterli olmadığı uç durumlar içindir ve boya döngüsünü beklememiz önemlidir. Hatta birkaç sürümleri geri dönüyor, ben yeni sürümleri ve eski bir keman oluşturmaya çalıştım, ama sorunu göstermek için karmaşık yeterince bileşeni oluşturmak için görünüyor olabilir ...
Graham P Heath

3
@neptunian Kesinlikle konuşmak gerekirse "[RAF] bir sonraki boyamadan önce [...] denir ..." - [ developer.mozilla.org/en-US/Apps/Fundamentals/Performance/… ]. Bu durumda, düğümün yerleşiminin hala DOM tarafından hesaplanması gerekir (diğer adıyla "yeniden akıtılan"). Bu, RAF'ı mizanpajdan önce mizanpaja sonra atlamak için kullanır. Elm tarayıcı belgeleri daha fazlası için iyi bir yerdir: elmprogramming.com/virtual-dom.html#how-browsers-render-html
Graham P Heath

1
_this.getDOMNode is not a functionBu kod ne halt?
OZZIE

104

Deneyimlerime window.requestAnimationFramegöre, DOM'un tamamen oluşturulduğundan / yeniden düzenlendiğinden emin olmak için yeterli değildi componentDidMount. Bir componentDidMountçağrı hemen sonra DOM erişen ve yalnızca kullanarak window.requestAnimationFrameDOM öğesinde kullanılabilir sonuç kod çalıştıran kod var ; ancak, bir yeniden akış henüz gerçekleşmediği için öğenin boyutlarındaki güncellemeler henüz yansıtılmamıştır.

Bunun işe yaraması için tek gerçekten güvenilir yol , bir sonraki karenin oluşturulmasına kaydolmadan önce React'in geçerli çağrı yığınının temizlenmesini sağlamak için yöntemimi a setTimeoutve window.requestAnimationFramea'ya sarmaktı.

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

Bunun neden oluştuğu / gerekli olduğu üzerine spekülasyon yapmak zorunda kalsaydım, DOM güncellemelerini topluyor ve mevcut yığın tamamlanana kadar DOM'da değişiklikleri uygulayamıyorum.

Sonuçta, React geri aramalarından sonra tetiklediğiniz kodda DOM ölçümleri kullanıyorsanız, muhtemelen bu yöntemi kullanmak istersiniz.


1
Yalnızca setTimeout VEYA requestAnimationFrame'e ihtiyacınız vardır, ikisine birden değil.

7
Genellikle doğru. Ancak, React'ın componentDidMount yöntemi bağlamında, bu yığın bitmeden önce bir requestAnimationFrame eklerseniz DOM gerçekten tam olarak güncellenmeyebilir. Sürekli olarak React geri aramalar bağlamında bu davranışı yeniden kodu var. DOM güncellendikten sonra kodunuzun yürütüldüğünden emin olmanın tek yolu (bir kez daha, bu özel React use-case), setTimeout ile ilk olarak çağrı yığınını temizlemektir.
Elliot Chong

6
Yukarıda, aynı geçici çözüme ihtiyaç duyan diğer açıklamaları göreceksiniz, yani: stackoverflow.com/questions/26556436/react-after-render-code/… Bu React kullanım örneği için yalnızca% 100 güvenilir yöntemdir. Bir tahminde bulunmam gerekiyorsa, mevcut yığın içinde potansiyel olarak uygulanmayan tepki toplu güncellemelerinin kendileri olabilir (bu nedenle, toplu işin uygulanmasını sağlamak için requestAnimationFrame'i bir sonraki çerçeveye erteler).
Elliot Chong

4
Sanırım JS dahili araçlarınızı fırçalamanız gerekebilir ... altitudelabs.com/blog/what-is-the-javascript-event-loop stackoverflow.com/questions/8058612/…
Elliot Chong

2
Mevcut çağrı yığınının temizlenmesini beklemek, olay döngüsü hakkında konuşmanın "saçma" bir yolu değil, sanırım her birine kendi ...
Elliot Chong

17

Bu soruyu yeni Kanca yöntemleriyle biraz güncellemek için useEffectkancayı kullanabilirsiniz :

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. 
     [])
}

Ayrıca düzen boyasından önce çalıştırmak istiyorsanız useLayoutEffectkancayı kullanın :

import React, { useLayoutEffect } from 'react'

export default function App(props) {

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

React belgelerine göre useLayoutEffect, tüm DOM mutasyonları reaktjs.org/docs/hooks-reference.html#uselayouteffect'ten sonra gerçekleşir
Philippe Hebert

Doğru, ancak düzen Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.düzenleyeceğim boya şansı olmadan önce çalışır .
P Fuster

UseEffect'in tarayıcının yeniden akışından sonra çalışıp çalışmadığını biliyor musunuz (React 'boya' adını vermez)? UseEffect ile bir öğe scrollHeight istemek güvenli midir?
eMontielG

Effect
P Fuster

13

Durumu değiştirebilir ve ardından hesaplamalarınızı setState geri aramasında yapabilirsiniz . React belgelerine göre, bunun "güncelleme uygulandıktan sonra ateşlenmesi garanti edilir".

Bu componentDidMount, yapıcıdan ziyade kodda veya başka bir yerde (yeniden boyutlandırma olay işleyicisinde olduğu gibi) yapılmalıdır .

Bu iyi bir alternatiftir window.requestAnimationFrameve bazı kullanıcıların burada bahsettiği sorunlara sahip değildir (onu birleştirmek setTimeoutveya birden çok kez çağırmak gerekir). Örneğin:

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
Bu benim en sevdiğim cevap. Temiz ve iyi deyimsel Reaksiyon kodu.
phatmann

1
Bu harika bir cevap! Teşekkürler!
Bryan Jyh Herng Chong

1
Bu güzel, bu yorumu oylayın!
bingeScripter

11

Bu çözümün kirli olduğunu hissediyorum, ama işte başlıyoruz:

componentDidMount() {
    this.componentDidUpdate()
}

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

Şimdi burada oturup aşağı oyları bekleyeceğim.


5
Bir React bileşeni yaşam döngüsüne saygı göstermelisiniz.
Túbal Martín

2
@ TúbalMartín biliyorum. Aynı sonuca ulaşmak için daha iyi bir yolunuz varsa, paylaşmaktan çekinmeyin.
Jaakko Karhu

8
"Buraya otur ve aşağı oyları bekle" için mecazi bir +1. Cesur adam. ; ^)
ruffin

14
Her iki yaşam döngüsünden bir yöntem çağırmak yerine, diğer döngülerden döngüleri tetiklemeniz gerekmez.
Tjorriemorrie

1
componentWillReceiveProps bunu yapmalı
Pablo

8

React, bu durumlarda yardımcı olan az sayıda yaşam döngüsü yöntemine sahiptir; getInitialState, getDefaultProps, componentWillMount, componentDidMount vb.

Sizin durumunuzda ve DOM öğeleriyle etkileşime girmesi gereken durumlarda, dom hazır olana kadar beklemeniz gerekir, bu nedenle componentDidMount'u aşağıdaki gibi kullanın :

/** @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;

Ayrıca reaksiyondaki yaşam döngüsü hakkında daha fazla bilgi için aşağıdaki bağlantıya bakabilirsiniz: https://facebook.github.io/react/docs/state-and-lifecycle.html

getInitialState, getDefaultProps, componentWillMount, componentDidMount


bileşenim sayfa görüntülemeden önce bir api çağrısı veri yüklerken büyük bir gecikmeye neden olan montaj çalışır.
Jason G

6

Ben de aynı problemle karşılaştım.

Çoğu senaryoda kesmek-ish kullanarak setTimeout(() => { }, 0)içinde componentDidMount()çalıştı.

Ancak özel bir durumda değil; ve ReachDOM findDOMNodebelgelerin söylediklerinden beri kullanmak istemedim :

Not: findDOMNode, temeldeki DOM düğümüne erişmek için kullanılan bir çıkış kapağıdır. Çoğu durumda, bu kaçış kapağının kullanılması önerilmez, çünkü bileşen soyutlamasını deler.

(Kaynak: findDOMNode )

Bu belirli bileşende componentDidUpdate()olayı kullanmak zorunda kaldım , bu yüzden kodum böyle oldu:

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

Ve sonra:

componentDidUpdate() {
    this.updateDimensions();
}

Son olarak, benim durumumda, oluşturulan dinleyiciyi kaldırmak zorunda kaldım componentDidMount:

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

2

Oluşturmadan sonra yüksekliği aşağıdaki gibi belirtebilir ve karşılık gelen reaksiyon bileşenlerinin yüksekliğini belirtebilirsiniz.

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

ya da sass kullanarak her bir reaksiyon bileşeninin yüksekliğini belirleyebilirsiniz. Sabit genişlikte ilk 2 reaksiyon bileşen ana div'ini ve daha sonra üçüncü bileşen ana div'in yüksekliğini otomatik olarak belirtin. Üçüncü div'in içeriğine bağlı olarak yükseklik atanacaktır.


2

Aslında benzer davranış ile bir sorun yaşıyorum, ben RenderDOM.render () sona erdiğinde yer tutucusu bulmak için kimliğe ihtiyaç duyan bir eklenti yükler ve onu bulmak için başarısız bir bileşen bu bir id bileşeniyle render.

ComponentDidMount () içindeki 0 ms ile setTimeout bunu düzeltti :)

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

1

Gönderen ReactDOM.render () belgelerinde:

İsteğe bağlı geri arama sağlanırsa, bileşen oluşturulduktan veya güncellendikten sonra yürütülür.


9
bunun nasıl kullanılacağına dair bir örnek ekleyebilir misiniz? Çoğunlukla render yönteminden elemanlar döndürüyorum, render ve call değerleri vermiyorum.
dcsan

23
Maalesef bahsettiğiniz geri arama, yalnızca üst ReactDOM.renderdüzeyReactElement.render için, bileşen seviyesinin (burada konu) için mevcut değildir.
Bramus

1
Burada örnek yardımcı olacaktır
DanV

1
Cevabınızdaki bağlantıyı tıkladım ve alıntıladığınız satırı bulamadım ve yanıtınız onsuz çalışmak için yeterli bilgi içermiyor. İyi bir soru yazma konusunda tavsiye için lütfen stackoverflow.com/help/how-to-answer adresine bakın
Benubird

1

Benim için, window.requestAnimationFrameveyasetTimeout tutarlı sonuçlar üretti. Bazen işe yaradı, ama her zaman değil - ya da bazen çok geç olurdu.

window.requestAnimationFrameGerektiği kadar döngü yaparak düzelttim .
(Genellikle 0 veya 2-3 kez)

Anahtar diff > 0: burada tam olarak sayfanın ne zaman güncellendiğini garanti edebiliriz.

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

Tuval üzerine büyük miktarda veri ve boya alan reaksiyon bileşenini yazdırmam gerektiğinde garip bir durum yaşadım. Belirtilen tüm yaklaşımları denedim, bunların hiçbiri benim için güvenilir bir şekilde çalıştı, setTimeout içinde requestAnimationFrame i% 20 oranında boş tuval alıyorum, bu yüzden aşağıdakileri yaptım:

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

Temelde requestAnimationFrame bir zincir yaptı, emin değilim bu iyi fikir ya da değil ama bu benim için vakaların% 100'ünde çalışıyor (30 i n değişkeni için bir değer olarak kullanıyorum).


0

Aslında istek animasyon karesi veya zaman aşımlarını kullanmaktan çok daha basit ve daha temiz bir sürümü var. Ben kimsenin getirmediğini şaşırdım: vanilya-js onload işleyicisi. Yapabiliyorsanız, bileşen montajı kullanın, eğer değilse, jsx bileşeninin onload hanlder'ında bir işlevi bağlayın. İşlevin her oluşturmayı çalıştırmasını istiyorsanız, siz de döndürmeden önce onu işlevin sonucunu verir. kod şöyle görünecektir:

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

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

}


-1

ES6Sınıflar yerine biraz güncellemeReact.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>
    );
  }
}

Ayrıca - React bileşeni yaşam döngüsü yöntemlerini kontrol edin: Bileşen Yaşam Döngüsü

Her bileşen, componentDidMountörn.

  • componentWillUnmount() - bileşen tarayıcı DOM'dan kaldırılmak üzere

1
Saygısızlık yok, ama bu soruya nasıl cevap veriyor? ES6'da bir güncelleme göstermek gerçekten soruyla ilgili değil / hiçbir şeyi değiştirmiyor. Daha eski yanıtların tümü, componentDidMount'un kendi başına nasıl çalışmadığı hakkında zaten konuşur.
dave4jr
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.