Короткий ответ:
React гарантирует, что ссылки установлены раньше componentDidMount
или componentDidUpdate
хуки. Но только для детей, которые действительно были обработаны .
componentDidMount() {
}
componentDidUpdate() {
}
render() {
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
сработаете.
Остерегайтесь других компонентов
Обратите внимание: если вы передадите дочерние элементы с refs другим компонентам, есть вероятность, что они делают что-то, что предотвращает рендеринг (и вызывает проблему).
Например, это:
<MyPanel>
<div ref={this.setRef} />
</MyPanel>
не будет работать, если MyPanel
не включить props.children
в его вывод:
function MyPanel(props) {
return <h1>Oops, no refs for you today!</h1>;
}
Опять же, это не ошибка: React не на что будет устанавливать ссылку, потому что элемент 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>
с ref фактически включается в вывод рендеринга.
Поэтому, если вы столкнулись с этой проблемой, вам необходимо убедиться, что между вашим компонентом и ссылкой нет ничего, что могло бы задержать рендеринг дочерних элементов.
Не использовать setState
для хранения ссылок
Убедитесь, что вы не используете setState
для сохранения ref в обратном вызове ref, так как он асинхронный и до того, как он "завершится", componentDidMount
будет выполнен первым.
Все еще проблема?
Если ни один из приведенных выше советов не помог, сообщите о проблеме в React, и мы рассмотрим.
this
из лексической области за пределами вашего класса. Попробуйте избавиться от синтаксиса стрелочной функции для методов вашего класса и посмотрите, поможет ли это.