componentDidMount在引用回调之前被调用


86

问题

我正在ref使用内联函数定义设置反应

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

然后在componentDidMountDOM引用中未设置

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

我的理解是,ref回调应在安装期间运行,但是在ref回调函数之前调用添加console.log语句揭示。componentDidMount

例如,我看过的其他代码示例在github上的讨论都表明相同的假设,componentDidMount应该在中定义的任何回调之后ref调用render,甚至在对话中也要说明

那么在所有的ref回调都执行完之后,componentDidMount是否被触发?

是。

我正在使用反应15.4.1

我尝试过的其他事情

为了验证该ref函数是否已被调用,我尝试在类上将其定义为

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

然后在 render

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

在这种情况下,控制台记录显示回调确实在之后被调用 componentDidMount


6
我可能错了,但是当您使用箭头函数作为渲染方法时,它将this从类外部的词法作用域中捕获值。尝试摆脱类方法的箭头函数语法,看看是否有帮助。
Yoshi

3
@GProst这就是我的问题的本质。我将console.log放在函数中,并且componentDidMount首先运行,然后是ref回调。
quickshiftin

3
只是有一个类似的问题-对我们来说,基本上,我们在一开始就错过了它render,因此需要利用componentDidUpdate,因为componentDidMount这不是更新生命周期的一部分。可能不是您的问题,但认为它可能值得提出作为潜在的解决方案。
亚历山大·尼德

4
与React 16相同。文档明确指出,ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.但这似乎不正确:(
Ryan H.

1
1.引用箭头声明是:ref = {ref => { this.drawerRef = ref }}2.甚至在componentDidMount之前调用引用;只有在您的情况下渲染div时,才能在初始渲染后访问ref。因此,您必须能够在下一级(即在componentWillReceiveProps中使用this.drawerRef3)访问ref 。如果尝试在初始安装之前进行访问,则只会获得未定义的ref值。
bh4r4th

Answers:


153

简短答案:

React保证ref设置在之前componentDidMountcomponentDidUpdate钩子。但仅适用于实际上被渲染过的孩子

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

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

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

请注意,这并不意味着“反应始终设置所有在运行这些挂钩之前引用”。
让我们看一些没有设置参考的示例。


引用未设置为未渲染的元素

React只会为您实际使用的元素调用ref回调 从render返回的

这意味着如果您的代码看起来像

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

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

首先this.state.isLoadingtrue,您应该希望this._setRef之前调用componentDidMount

这应该是有道理的:如果您的第一个渲染器返回了<h1>Loading</h1>,React无法知道在其他条件下它会返回需要附加引用的其他内容。也有没有将ref设置为的内容:<div>元素未创建,因为render()方法表示不应渲染该元素。

因此,在此示例中,只会componentDidMount触发。但是,this.state.loading更改为时false,您会看到this._setRef首先附件,然后componentDidUpdate将其触发。


提防其他组件

注意 如果您将带有ref的子级传递给其他组件,则它们可能会执行某些阻止渲染的操作(并导致问题)。

例如,这:

<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没有任何设置引用的原因,因为DOM元素没有创建


如果将引用传递给嵌套,则它们不会在生命周期之前设置 ReactDOM.render()

与上一节类似,如果将带有ref的孩子传递到另一个组件,则此组件可能会执行某些阻止及时附加ref的操作。

例如,也许不是从返回孩子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的我们实际上包含在渲染输出中。

因此,如果遇到此问题,则需要验证组件和ref之间没有任何东西可能会延迟渲染子级。

不要setState用来存储裁判

确保您setState不习惯将ref存储在ref回调中,因为它是异步的,并且在“完成”之前componentDidMount将首先执行。


还是一个问题?

如果以上提示均不能解决问题,请在React中提出问题,我们将进行调查。


2
我也对答案做了修改,以解释这种情况。请参阅第一部分。希望这可以帮助!
丹·阿布拉莫夫

@DanAbramov嗨,谢谢!不幸的是,当我第一次遇到这个案例时,我无法开发一个可复制的案例。可悲的是,我不再从事那个项目,从那以后一直无法复制。我同意,这个问题已变得足够普遍,试图找到可重复的案例是关键,因为许多人似乎都在经历这个问题。
quickshiftin

我认为在很多情况下,这可能是由于误解造成的。在React 15中,这也可能由于错误被吞没而发生(反应16具有更好的错误处理并防止了这种情况)。发生这种情况时,我很乐意复查更多案例,请随时在评论中添加它们。
Dan Abramov '18

有帮助!我没有真正注意到有一个预加载器。
Nazariy

1
这个答案确实帮助了我。我当时在为一些空的“ refs”引用而苦苦挣扎,结果,这些“ elements”根本没有被渲染。
MarkSkayff '19

1

对问题的另一种观察。

我已经意识到问题仅在开发模式下发生。经过更多调查,我发现react-hot-loader在Webpack配置中禁用可避免此问题。

我在用

  • “ react-hot-loader”:“ 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}}/>
    )
  }
}

react-hot-loader崩溃(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

当您尝试使用未安装组件的ref时(例如在setinterval中使用ref)并且在组件卸载期间未清除设置间隔时,也会出现此问题。

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

总是清除间隔,例如

componentWillUnmount(){
    clearInterval(interval_holder)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.