componentDidMount wywołane PRZED wywołaniem zwrotnym ref

86

Problem

Ustawiam odpowiedź, refużywając definicji funkcji wbudowanej

render = () => {
    return (
        <div className="drawer" ref={drawer => this.drawerRef = drawer}>

wtedy w componentDidMountDOM odwołanie nie jest ustawione

componentDidMount = () => {
    // this.drawerRef is not defined

Rozumiem, że refwywołanie zwrotne powinno być uruchamiane podczas montowania, jednak dodawanie console.loginstrukcji reveals componentDidMountjest wywoływane przed funkcją ref callback.

Inne przykłady kodu, które przeglądałem, na przykład ta dyskusja na githubie wskazują na to samo założenie, componentDidMountnależy wywołać po każdym refwywołaniu zwrotnym zdefiniowanym w render, jest to nawet podane w rozmowie

Czyli componentDidMount jest uruchamiane po wykonaniu wszystkich wywołań zwrotnych?

Tak.

Korzystam z React 15.4.1

Próbowałem czegoś innego

Aby sprawdzić, czy reffunkcja została wywołana, próbowałem zdefiniować ją w klasie jako takiej

setDrawerRef = (drawer) => {
  this.drawerRef = drawer;
}

potem w render

<div className="drawer" ref={this.setDrawerRef}>

Rejestrowanie konsoli w tym przypadku ujawnia, że ​​wywołanie zwrotne jest rzeczywiście wywoływane po componentDidMount

quickshiftin
źródło
6
Może się mylę, ale kiedy używasz funkcji strzałek do renderowania, przechwytuje ona wartość thisz zakresu leksykalnego poza twoją klasą. Spróbuj pozbyć się składni funkcji strzałek dla metod klasy i zobacz, czy to pomaga.
Yoshi
3
@GProst Taka jest natura mojego pytania. Umieściłem console.log w obu funkcjach i najpierw działa componentDidMount, a drugie wywołanie zwrotne ref.
quickshift w
3
Właśnie miałem podobny problem - zasadniczo przegapiliśmy go na początku renderi dlatego musieliśmy go wykorzystać componentDidUpdate, ponieważ componentDidMountnie jest to część cyklu aktualizacji . Prawdopodobnie nie jest to twój problem, ale pomyślałem, że warto go poruszyć jako potencjalne rozwiązanie.
Alexander Nied,
4
To samo z Reactem 16. Dokumentacja jasno stwierdza, ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.ale to nie wydaje się być prawdą :(
Ryan H.
1
1. deklaracja strzałki ref to: ref = {ref => { this.drawerRef = ref }}2. parzyste referencje są wywoływane przed komponentem componentDidMount; ref można uzyskać tylko po początkowym renderowaniu, gdy renderowany jest element div w Twoim przypadku. Musisz więc mieć dostęp do ref na następnym poziomie, tj. W componentWillReceiveProps przy użyciu this.drawerRef3. Jeśli spróbujesz uzyskać dostęp przed pierwszym zamontowaniem, otrzymasz tylko niezdefiniowane wartości ref.
bh4r4

Odpowiedzi:

153

Krótka odpowiedź:

React gwarantuje, że refs są ustawione przed componentDidMountlub componentDidUpdatehooki. Ale tylko dla dzieci, które faktycznie zostały wyrenderowane .

componentDidMount() {
  // can use any refs here
}

componentDidUpdate() {
  // can use any refs here
}

render() {
  // as long as those refs were rendered!
  return <div ref={/* ... */} />;
}

Zauważ, że nie oznacza to, że „Reakcja zawsze ustawia wszystkie odniesienia przed uruchomieniem tych haków”.
Spójrzmy na kilka przykładów, w których referencje nie są ustawiane.


Odniesienia nie są ustawiane dla elementów, które nie zostały wyrenderowane

React wywoła wywołania zwrotne tylko dla elementów, które faktycznie zwróciłeś z renderowania .

Oznacza to, że jeśli twój kod wygląda jak

render() {
  if (this.state.isLoading) {
    return <h1>Loading</h1>;
  }

  return <div ref={this._setRef} />;
}

i początkowo this.state.isLoadingjest true, należy nie oczekiwać this._setRef, aby być wywołana przed componentDidMount.

To powinno mieć sens: jeśli twój pierwszy render został zwrócony <h1>Loading</h1>, nie ma możliwości, aby React wiedział, że pod jakimś innym warunkiem zwraca coś innego, co wymaga dołączenia ref. Jest też nic, aby ustawić wg:<div> element nie został utworzony, ponieważ render()metoda powiedział, że nie powinno być renderowane.

Więc w tym przykładzie tylko componentDidMountodpali. Jednak po this.state.loadingzmianie nafalse , najpierw zobaczysz this._setRefzałączony, a następnie componentDidUpdatezostanie uruchomiony.


Uważaj na inne komponenty

Zauważ, że jeśli przekażesz dzieciom z referencjami do innych komponentów , jest szansa, że ​​robią coś, co uniemożliwia renderowanie (i powoduje problem).

Na przykład to:

<MyPanel>
  <div ref={this.setRef} />
</MyPanel>

nie działałby, gdyby MyPanelnie uwzględniał props.childrenw swoim wyjściu:

function MyPanel(props) {
  // ignore props.children
  return <h1>Oops, no refs for you today!</h1>;
}

Ponownie, to nie jest błąd: React nie miałby nic do ustawienia ref, ponieważ element DOM nie został utworzony .


Odniesienia nie są ustawiane przed cyklami życia, jeśli są przekazywane do zagnieżdżonego ReactDOM.render()

Podobnie jak w poprzedniej sekcji, jeśli przekazujesz element potomny z ref do innego składnika, możliwe jest, że ten składnik może zrobić coś, co uniemożliwia dołączenie ref w czasie.

Na przykład może nie zwraca dziecka z render(), a zamiast tego wywołuje ReactDOM.render()hak cyklu życia. Przykład tego można znaleźć tutaj . W tym przykładzie renderujemy:

<MyModal>
  <div ref={this.setRef} />
</MyModal>

Ale MyModalwykonuje ReactDOM.render()wywołanie w swojej componentDidUpdate metodzie cyklu życia:

componentDidUpdate() {
  ReactDOM.render(this.props.children, this.targetEl);
}

render() {
  return null;
}

Od czasu React 16 takie wywołania renderowania najwyższego poziomu podczas cyklu życia będą opóźnione do czasu, gdy cykle życia zostaną uruchomione dla całego drzewa . To by wyjaśniało, dlaczego nie widzisz odnośników dołączonych na czas.

Rozwiązaniem tego problemu jest użycie portali zamiast zagnieżdżonych ReactDOM.renderwywołań:

render() {
  return ReactDOM.createPortal(this.props.children, this.targetEl);
}

W ten sposób nasz <div>z ref jest faktycznie uwzględniany w wyniku renderowania.

Jeśli więc napotkasz ten problem, musisz sprawdzić, czy między komponentem a referencją nie ma nic, co mogłoby opóźnić renderowanie elementów potomnych.

Nie używaj setStatedo przechowywania referencji

Upewnij się, że nie używasz setStatedo przechowywania ref w wywołaniu zwrotnym ref, ponieważ jest asynchroniczne i zanim zostanie „zakończone”, componentDidMountzostanie wykonane jako pierwsze.


Wciąż problem?

Jeśli żadna z powyższych wskazówek nie pomoże, zgłoś problem w Reakcie, a my się przyjrzymy.

Dan Abramov
źródło
2
Zredagowałem moją odpowiedź, aby wyjaśnić również tę sytuację. Zobacz pierwszą sekcję. Mam nadzieję że to pomoże!
Dan Abramov
Cześć @DanAbramov, dzięki za to! Niestety nie udało mi się opracować powtarzalnego przypadku, gdy pierwszy raz go spotkałem. Niestety nie pracuję już nad tym projektem i od tego czasu nie mogę go powtórzyć. Pytanie stało się jednak na tyle popularne, że zgadzam się, że próba znalezienia powtarzalnego przypadku jest kluczowa, ponieważ wydaje się, że wiele osób ma ten problem.
quickshift w kwietniu
Myślę, że w wielu przypadkach było to spowodowane nieporozumieniem. W Reakcie 15 może się to również zdarzyć z powodu błędu, który został połknięty (React 16 ma lepszą obsługę błędów i zapobiega temu). Chętnie omówię więcej przypadków, gdy tak się stanie, więc możesz je dodawać w komentarzach.
Dan Abramov
Pomaga! Tak naprawdę nie zauważyłem, że był preloader.
Nazariy
1
Ta odpowiedź naprawdę mi pomogła. Walczyłem z jakimś pustym odniesieniem „refs” i cóż, okazało się, że „elementy” w ogóle nie były renderowane.
MarkSkayff
1

Inna obserwacja problemu.

Zdałem sobie sprawę, że problem wystąpił tylko w trybie programistycznym. Po dokładniejszym zbadaniu stwierdziłem, że wyłączenie react-hot-loaderw konfiguracji WebPacka zapobiega temu problemowi.

ja używam

  • „Reaguj na gorąco”: „3.1.3”
  • "webpack": "4.10.2",

Jest to aplikacja elektronowa.

Moja częściowa konfiguracja deweloperska pakietu Webpack

const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.config.base')

module.exports = merge(baseConfig, {

  entry: [
    // REMOVED THIS -> 'react-hot-loader/patch',
    `webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
    '@babel/polyfill',
    './app/index'
  ],
  ...
})

Stało się podejrzane, gdy zobaczyłem, że użycie funkcji inline w render () działa, ale użycie metody związanej kończyło się awarią.

Działa w każdym przypadku

class MyComponent {
  render () {
    return (
      <input ref={(el) => {this.inputField = el}}/>
    )
  }
}

Awaria z reaktywnym programem ładującym (ref nie jest zdefiniowany w componentDidMount)

class MyComponent {
  constructor (props) {
    super(props)
    this.inputRef = this.inputRef.bind(this)
  }

  inputRef (input) {
    this.inputField = input
  }

  render () {
    return (
      <input ref={this.inputRef}/>
    )
  }
}

Szczerze mówiąc, ponowne ładowanie na gorąco często było problematyczne, aby uzyskać „właściwy”. Dzięki szybkiej aktualizacji narzędzi programistycznych każdy projekt ma inną konfigurację. Może moja konkretna konfiguracja mogłaby zostać naprawiona. Dam ci znać, jeśli tak jest.

Kev
źródło
To może wyjaśniać, dlaczego mam problemy z tym w CodePen, ale użycie funkcji inline nie pomogło w moim przypadku.
robartsd
0

Problem może się również pojawić, gdy spróbujesz użyć odwołania niezmontowanego komponentu, takiego jak użycie ref w setinterval i nie wyczyścisz ustawionego interwału podczas odmontowywania komponentu.

componentDidMount(){
    interval_holder = setInterval(() => {
    this.myref = "something";//accessing ref of a component
    }, 2000);
  }

zawsze wyraźny odstęp, jak na przykład

componentWillUnmount(){
    clearInterval(interval_holder)
}
koder losowy
źródło