Problème
Je suis en train de réagir ref
en utilisant une définition de fonction inline
render = () => {
return (
<div className="drawer" ref={drawer => this.drawerRef = drawer}>
alors dans componentDidMount
la référence DOM n'est pas définie
componentDidMount = () => {
// this.drawerRef is not defined
D'après ce que je comprends, le callback ref
devrait être exécuté pendant le montage, mais l'ajout d'instructions console.log
révèle que componentDidMount
est appelé before la fonction de rappel ref.
D'autres exemples de code que j'ai consultés, par exemple cette discussion sur github indiquent la même hypothèse, componentDidMount
devrait être appelé après tout rappel ref
défini dans render
, c'est même indiqué dans la conversation
Alors composantDidMount est déclenché après que tous les rappels de référence aient été exécuté?
Oui.
J'utilise react 15.4.1
Quelque chose d'autre que j'ai essayé
Pour vérifier que la fonction ref
était appelée, j'ai essayé de la définir dans la classe en tant que telle
setDrawerRef = (drawer) => {
this.drawerRef = drawer;
}
puis dans render
<div className="drawer" ref={this.setDrawerRef}>
Dans ce cas, la consignation de la console révèle que le rappel est effectivement appelé aftercomponentDidMount
Réponse courte:
React garantit que les références sont définies avant les hooks componentDidMount
ou componentDidUpdate
. Mais uniquement pour les enfants qui ont effectivement été rendus.
componentDidMount() {
// can use any refs here
}
componentDidUpdate() {
// can use any refs here
}
render() {
// as long as those refs were rendered!
return <div ref={/* ... */} />;
}
Notez que cela ne signifie pas “React définit toujours tout ref avant que ces hooks ne soient exécutés”.
Voyons quelques exemples où les références ne pas sont définies.
React appellera uniquement les rappels de référence pour les éléments que vous avez renvoyés de à partir de render.
Cela signifie que si votre code ressemble à
render() {
if (this.state.isLoading) {
return <h1>Loading</h1>;
}
return <div ref={this._setRef} />;
}
et initialement this.state.isLoading
est true
, vous devriez ne pas vous attendre à ce que this._setRef
soit appelé avant componentDidMount
.
Cela devrait avoir un sens: si votre premier rendu renvoie <h1>Loading</h1>
, React ne peut pas savoir que, sous une autre condition, il renvoie quelque chose qui nécessite l’ajout d’une référence. Il n’existe également que rien pour définir la référence sur: l’élément <div>
n’a pas été créé, car la méthode render()
a déclaré qu’il ne devait pas être restitué.
Donc, avec cet exemple, seule componentDidMount
sera déclenchée. Cependant, lorsque this.state.loading
devient false
, vous verrez d'abord this._setRef
attaché, puis componentDidUpdate
sera déclenché.
Notez que si vous transmettez les enfants avec des références à d’autres composants, il est possible qu’ils fassent quelque chose qui empêche le rendu (et cause le problème).
Par exemple, ceci:
<MyPanel>
<div ref={this.setRef} />
</MyPanel>
ne fonctionnerait pas si MyPanel
n'incluait pas props.children
dans sa sortie:
function MyPanel(props) {
// ignore props.children
return <h1>Oops, no refs for you today!</h1>;
}
Encore une fois, ce n’est pas un bug: il n’y aurait rien pour que React mette la référence à parce que l’élément DOM n’a pas été créé.
ReactDOM.render()
imbriquée.Comme dans la section précédente, si vous transmettez un enfant avec une référence à un autre composant, il est possible que ce composant fasse quelque chose qui empêche l’attachement de la référence dans le temps.
Par exemple, il se peut que l’enfant ne soit pas renvoyé de render()
et qu’il appelle plutôt ReactDOM.render()
dans un hook de cycle de vie. Vous pouvez trouver un exemple de ceci ici . Dans cet exemple, nous rendons:
<MyModal>
<div ref={this.setRef} />
</MyModal>
Mais MyModal
effectue un appel ReactDOM.render()
dans la méthode itscomponentDidUpdate
:
componentDidUpdate() {
ReactDOM.render(this.props.children, this.targetEl);
}
render() {
return null;
}
Depuis React 16, ces appels de rendu de niveau supérieur au cours d'un cycle de vie seront retardés jusqu'à ce que les cycles de vie se soient écoulés pour tout l'arbre. Cela expliquerait pourquoi vous ne voyez pas les références attachées à temps.
La solution à ce problème consiste à utiliser portals au lieu d'appels ReactDOM.render
imbriqués:
render() {
return ReactDOM.createPortal(this.props.children, this.targetEl);
}
De cette façon, notre <div>
avec une référence est réellement inclus dans le rendu.
Par conséquent, si vous rencontrez ce problème, vous devez vérifier qu’il n’ya rien entre votre composant et la référence qui puisse retarder le rendu des enfants.
setState
pour stocker des référencesAssurez-vous de ne pas utiliser setState
pour stocker le ref ref dans le rappel, car il est asynchrone et avant qu'il soit "fini", componentDidMount
sera exécuté en premier.
Si aucun des conseils ci-dessus ne vous aide, déposez un problème dans React et nous examinerons.
Une observation différente du problème.
J'ai réalisé que le problème ne se produisait qu'en mode de développement . Après une enquête plus approfondie, j'ai constaté que la désactivation de react-hot-loader
dans ma configuration Webpack évite ce problème.
J'utilise
Et c'est une application électronique.
Ma configuration partielle de développement 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'
],
...
})
Cela est devenu suspect quand j'ai vu que l'utilisation de la fonction inline dans render () fonctionnait, mais que l'utilisation d'une méthode liée plantait.
Fonctionne dans tous les cas
class MyComponent {
render () {
return (
<input ref={(el) => {this.inputField = el}}/>
)
}
}
Crash avec react-hot-loader (la référence n'est pas définie dans composantDidMount)
class MyComponent {
constructor (props) {
super(props)
this.inputRef = this.inputRef.bind(this)
}
inputRef (input) {
this.inputField = input
}
render () {
return (
<input ref={this.inputRef}/>
)
}
}
Pour être honnête, le rechargement à chaud a souvent été problématique pour obtenir "le droit". Avec la mise à jour rapide des outils de développement, chaque projet a une configuration différente ..__ Peut-être que ma configuration particulière pourrait être corrigée. Je vous ferai savoir ici si c'est le cas.