componentDidMount, що викликається ПЕРЕД зворотним викликом


86

Проблема

Я встановлюю реакцію refза допомогою вбудованого визначення функції

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

тоді в componentDidMountDOM посилання не встановлено

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

Я розумію, що refзворотний виклик повинен виконуватися під час монтування, однак додавання console.logоператорів reveals 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


6
Можливо, я помиляюся, але коли ви використовуєте функцію стрілки для методу візуалізації, вона буде фіксувати значення thisз лексичної області за межами вашого класу. Спробуйте позбутися синтаксису функції стрілки для методів вашого класу і перевірте, чи це допомагає.
Йоші

3
@GProst Це характер мого запитання. Я помістив console.log в обидві функції, і componentDidMount працює спочатку, зворотний виклик ref - другий.
quickshiftin

3
Просто мала схожу проблему - для нас, в основному, ми пропустили її на початковому етапі, renderі, отже, нам потрібно було задіяти componentDidUpdate, оскільки componentDidMountце не є частиною життєвого циклу оновлення . Можливо, це не ваша проблема, але вважав, що, можливо, варто підняти її як можливе рішення.
Alexander Nied

4
Те саме з React 16. У документації чітко зазначено, ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.але це, схоже, не відповідає дійсності :(
Райан Х.

1
1. декларація зі стрілкою ref є: ref = {ref => { this.drawerRef = ref }}2. навіть посилання ref викликаються перед componentDidMount; ref можна отримати доступ лише після початкового візуалізації, коли div у вашому випадку відображається. Отже, ви повинні мати доступ до ref на наступному рівні, тобто в componentWillReceiveProps за допомогою this.drawerRef3. Якщо ви спробуєте отримати доступ до початкового монтування, ви отримаєте лише невизначені значення ref.
bh4r4th

Відповіді:


153

Коротка відповідь:

Гарантуйте React, що посилання встановлюються перед 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 завжди встановлює всі посилання перед тим, як ці гачки запускатимуться".
Давайте розглянемо кілька прикладів, коли посилання не встановлюються.


Посилання не встановлюються для елементів, які не були відображені

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 не може знати, що за якоїсь іншої умови він повертає щось інше, що потребує прикріплення посилання. Там немає також нічого встановити реф на:<div> елемент не був створений , так як render()метод сказав , що це не повинно бути надано.

Тож у цьому прикладі componentDidMountбуде спрацьовувати лише . Однак, коли this.state.loadingзміниться наfalse , this._setRefспочатку ви побачите вкладене, а потім componentDidUpdateвикличеться.


Слідкуйте за іншими компонентами

Зверніть увагу, що якщо ви передаєте дочірні елементи з реф. До інших компонентів, існує ймовірність, що вони роблять щось, що заважає візуалізації (і спричиняє проблему).

Наприклад, це:

<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 не був створений .


Посилання не встановлюються перед життєвими циклами, якщо вони передаються вкладеному ReactDOM.render()

Подібно до попереднього розділу, якщо ви передаєте дитину з посиланням на інший компонент, можливо, цей компонент може зробити щось, що заважає приєднати посилання вчасно.

Наприклад, можливо, це не повернення дитини з 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>з посиланням фактично включається у вивід рендеринга.

Отже, якщо ви зіткнулися з цією проблемою, вам потрібно переконатися, що між вашим компонентом та посиланням немає нічого, що може затримати візуалізацію дочірніх елементів.

Не використовуйте setStateдля зберігання посилань

Переконайтесь, що ви не використовуєте setStateдля зберігання посилання у зворотному виклику ref, оскільки він є асинхронним, і до того, як він буде «закінчений», componentDidMountбуде виконаний першим.


Все ще є проблемою?

Якщо жодна з наведених вище порад не допоможе, подайте проблему в React, і ми подивимось.


2
Я зробив редагування своєї відповіді, щоб також пояснити цю ситуацію. Див. Перший розділ. Сподіваюся, це допомагає!
Ден Абрамов

Привіт @DanAbramov дякую за це! На жаль, я не зміг розробити відтворюваний випадок, коли вперше зіткнувся з ним. На жаль, я більше не працюю над цим проектом і з тих пір не маю змоги дорікати. Питання, однак, стало досить популярним, і я погоджуюся, що спроба знайти відтворюваний випадок є ключовою, оскільки багато людей, здається, стикаються з проблемою.
quickshiftin

Думаю, у багатьох випадках це сталося через непорозуміння. У React 15 це також може статися через помилку, яку проковтнули (React 16 має кращу обробку помилок і запобігає цьому). Я із задоволенням переглядаю більше випадків, коли це трапляється, тому сміливо додайте їх у коментарі.
Ден Абрамов

Допомагає! Я не дуже помітив, що є попередній завантажувач.
Назарій

1
Ця відповідь мені справді допомогла. Я боровся з деякими пустими посиланнями "refs", і добре, виявилося, що "елементи" взагалі не відображались.
MarkSkayff

1

Інше спостереження за проблемою.

Я зрозумів, що проблема виникла лише в режимі розробки. Після подальшого розслідування я виявив, що вимкнення 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 не визначено в componentDidMount)

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

  inputRef (input) {
    this.inputField = input
  }

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

Чесно кажучи, гаряче перезавантаження часто було проблематично отримати "правильно". Завдяки інструментам розробника, які швидко оновлюються, кожен проект має іншу конфігурацію. Можливо, мій конкретний конфігуратор може бути виправлений. Я повідомлю вас тут, якщо це так.


Це може пояснити, чому у мене виникають проблеми з цим у CodePen, але використання вбудованої функції в моєму випадку не допомогло.
robartsd

0

Проблема також може виникнути, коли ви намагаєтесь використовувати посилання немонтованого компонента, як використання посилання у встановленому інтервалі, і не очищаєте встановлений інтервал під час демонтажу компонента.

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

завжди чіткий інтервал, як, наприклад,

componentWillUnmount(){
    clearInterval(interval_holder)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.