ComponentDidMount называется BEFORE refback

Проблема

Я устанавливаю реакцию ref с помощью встроенного определения функции

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

то в componentDidMount ссылка DOM не установлена ​​

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

Мое понимание - обратный вызов ref должен выполняться во время монтирования, однако добавление операторов console.log показывает, что componentDidMount называется до функцией обратного вызова ref.

Другие примеры кода, на которые я смотрел, например, это обсуждение на github указывают одно и то же предположение, componentDidMount следует называть после любых ref обратных вызовов, определенных в render, он даже указанных в разговоре

Так что компонентныйDidMount запускается после того, как все обратные вызовы ref   был выполнен?

Да.

Я использую реакцию 15.4.1

Что-то еще я пробовал

Чтобы проверить, что функция ref вызывается, я попытался определить ее в классе как таковой

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

то в render

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

Запись в консоли в этом случае показывает, что обратный вызов действительно называется после componentDidMount

Ответ 1

Короткий ответ:

React гарантирует, что refs устанавливаются перед hook- componentDidUpdate componentDidMount или componentDidUpdate. Но только для детей, которые действительно получили.

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

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

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

Обратите внимание, что это не означает, что "React всегда устанавливает все значения refs перед запуском этих крючков".
Давайте посмотрим на некоторые примеры, когда refs не устанавливаются.


Refs dont get set для элементов, которые werent rendered

React будет вызывать только обратные вызовы ref для элементов, которые вы фактически вернули из рендеринга.

Это означает, что если ваш код выглядит

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

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

и первоначально this.state.isLoading true, вы не должны ожидать, что this._setRef будет вызван перед componentDidMount.

Это должно иметь смысл: если ваш первый рендер возвратил <h1>Loading</h1>, для React не существует возможного способа узнать, что при каком-либо другом условии он возвращает что-то еще, для которого требуется привязка ref. Также нет ничего, чтобы установить ref: элемент <div> не был создан, потому что метод render() сказал, что он не должен отображаться.

Таким образом, в этом примере будет запущен только componentDidMount. Однако, когда this.state.loading изменится на false, вы увидите, что this._setRef прикреплен первым, а затем будет this._setRef componentDidUpdate.


Следите за другими компонентами

Обратите внимание: если вы передаете детям с refs до других компонентов, есть шанс, что они делают что-то, что предотвращает рендеринг (и вызывает проблему).

Например, это:

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

не будет работать, если MyPanel не включил props.children в свой вывод:

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

Опять же, это не ошибка: для React не было бы никакого задания ref, потому что элемент DOM не был создан.


Refs dont get set before lifecycles, если theyre передано вложенное ReactDOM.render()

Как и в предыдущем разделе, если вы передаете дочерний элемент с ссылкой на другой компонент, возможно, что этот компонент может сделать что-то, что предотвращает своевременное добавление ссылки.

Например, возможно, что он не возвращает ребенка из render(), а вместо этого вызывает ReactDOM.render() в ReactDOM.render() жизненного цикла. Вы можете найти пример этого здесь. В этом примере мы делаем:

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

Но MyModal выполняет ReactDOM.render() в своем жизненном цикле componentDidUpdate:

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

render() {
  return null;
}

Начиная с React 16, такие вызовы визуализации верхнего уровня в течение жизненного цикла будут отложены до тех пор, пока не будут выполняться жизненные циклы для всего дерева. Это объясняет, почему вы не видите ссылки, связанные со временем.

Решение этой проблемы заключается в использовании порталов вместо вложенных вызовов ReactDOM.render :

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

Таким образом, наш <div> с ref фактически включен в вывод рендеринга.

Поэтому, если вы столкнулись с этой проблемой, вам нужно проверить, нет ли между вашим компонентом и ссылкой, что может задержать рендеринг детей.

Не используйте setState для хранения setState

Убедитесь, что вы не используете setState для хранения ref в setState ref, так как он асинхронен и до его завершения " componentDidMount будет выполнен первым.


Еще проблема?

Если ни один из приведенных выше советов не поможет, напишите о проблеме в React, и мы посмотрим.

Ответ 2

Другое замечание проблемы.

Я понял, что проблема возникла только в режиме разработки. После дополнительного изучения, я обнаружил, что отключение react-hot-loader в моем WebPack конфигурации предотвращает эту проблему.

я использую

  • "реактивный нагреватель": "3.1.3"
  • "webpack": "4.10.2",

И это электронное приложение.

Моя частная конфигурация разработки 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'
  ],
  ...
})

Это стало подозрительным, когда я увидел, что использование встроенной функции в render() работает, но с использованием связанного метода происходит сбой.

Работает в любом случае

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

Сбой с помощью реактора с горячей перезагрузкой (ref undefined в компонентеDidMount)

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

  inputRef (input) {
    this.inputField = input
  }

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

Честно говоря, горячая перезагрузка часто была проблематичной, чтобы получить "право". Благодаря быстрому обновлению инструментов, каждый проект имеет другую конфигурацию. Возможно, моя конкретная конфигурация может быть исправлена. Я сообщу вам, если это произойдет.

Ответ 3

В componentDidMount вы можете найти свой элемент ref (div.drawer) в браузере DOM? Если нет, вы не можете получить ссылку. Поскольку проблема найдена в другом, более крупном коде, причиной может быть то, что элемент ref (div.drawer) не отображается.

Ответ 4

В 16.2.0 мне пришлось решить это с помощью setTimeout(()=>{this.doThing(this.myRef)}, 25)

:(

Если есть реальные решения, я очень хочу их услышать.

Ответ 5

Попробуйте объявить свой класс как...

class YourClass extends Component {
  render() {...same stuff you had...}
  componentDidMount() {...same stuff you had...}
}

Я думаю, что ваше определение класса использует определение компонента без состояния и заставляет жизненный цикл работать не так, как вы ожидали. Я немного удивлен, что компонентDidMount() вообще вызван, поэтому я могу неправильно понять ситуацию.