【问题标题】:componentDidMount called BEFORE ref callback在 ref 回调之前调用 componentDidMount
【发布时间】:2017-10-19 20:40:18
【问题描述】:

问题

我正在使用内联函数定义设置反应 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 上的 this discussion 表明相同的假设,componentDidMount 应该在render 中定义的任何 ref 回调之后调用 ,甚至stated in the conversation

所以 componentDidMount 在所有 ref 回调完成后被触发 被处决了?

是的。

我正在使用反应 15.4.1

我尝试过的其他方法

为了验证 ref 函数是否被调用,我尝试在类中定义它

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

然后在render

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

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

【问题讨论】:

  • 我可能是错的,但是当您使用箭头函数进行渲染时,它将从您的类之外的词法范围捕获this 的值。尝试摆脱类方法的箭头函数语法,看看是否有帮助。
  • @GProst 这就是我的问题的本质。我将 console.log 放在两个函数中,并且 componentDidMount 首先运行,然后是 ref 回调。
  • 刚刚遇到了类似的问题——对我们来说,基本上,我们在最初的render 上错过了它,因此需要利用componentDidUpdate,因为componentDidMount 不是更新的一部分@ 987654324@。可能不是您的问题,但认为它可能值得作为潜在的解决方案提出。
  • 与 React 16 相同。文档明确指出 ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks. 但这似乎不是真的:(
  • 1. ref 箭头声明为:ref = {ref =&gt; { this.drawerRef = ref }} 2. 甚至在 componentDidMount 之前调用 refs;当您的案例中的 div 被渲染时,只能在初始渲染后访问 ref 。因此,您必须能够访问下一级的 ref,即在 componentWillReceiveProps 中使用 this.drawerRef 3。如果您尝试在初始挂载之前访问,您将只能获得未定义的 ref 值。

标签: javascript reactjs


【解决方案1】:

简答:

React 保证 refs 设置在 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 总是在这些钩子运行之前设置 所有 refs”。
让我们看一些 refs 设置的例子。


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

React 只会为您实际从渲染返回的元素调用 ref 回调。

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

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

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

最初this.state.isLoadingtrue,您应该期望this._setRefcomponentDidMount 之前被调用。

这应该是有道理的:如果你的第一个渲染返回 &lt;h1&gt;Loading&lt;/h1&gt;,React 不可能知道在其他情况下它返回需要附加 ref 的其他内容。还有没有什么可以将 ref 设置为:&lt;div&gt; 元素没有被创建,因为render() 方法说它不应该被渲染。

所以在这个例子中,只有componentDidMount 会触发。但是,this.state.loading 更改为false时,您会看到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 元素没有被创建


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

与上一节类似,如果您将带有 ref 的子组件传递给另一个组件,则该组件可能会做一些阻止及时附加 ref 的事情。

例如,它可能不是从 render() 返回子节点,而是在生命周期挂钩中调用 ReactDOM.render()。你可以找到这个here 的一个例子。在那个例子中,我们渲染:

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

MyModal componentDidUpdate 生命周期方法中执行ReactDOM.render() 调用:

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

render() {
  return null;
}

从 React 16 开始,这样的生命周期中的顶级渲染调用将被延迟,直到整个树的生命周期都运行完。这可以解释为什么您没有及时看到附加的 refs。

解决这个问题的方法是使用 portals 而不是嵌套的 ReactDOM.render 调用:

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

这样,我们的 &lt;div&gt; 带有一个 ref 实际上包含在渲染输出中。

因此,如果您遇到此问题,则需要验证您的组件和 ref 之间没有任何可能延迟渲染子项的内容。

不要使用setState 来存储引用

确保您没有使用 setState 将 ref 存储在 ref 回调中,因为它是异步的,在“完成”之前,componentDidMount 将首先执行。


还有问题吗?

如果以上提示都没有帮助,请在 React 中提交问题,我们将进行查看。

【讨论】:

  • 我也对我的回答进行了编辑以解释这种情况。见第一节。希望这会有所帮助!
  • 嗨@DanAbramov,谢谢!不幸的是,当我第一次遇到它时,我无法开发出可重现的案例。可悲的是,我不再从事该项目,并且从那时起就无法复制。这个问题已经变得足够流行,我同意,试图找到可重现的案例是关键,因为许多人似乎都遇到了这个问题。
  • 我认为在很多情况下这很可能是由于误解。在 React 15 中,这也可能是由于错误被吞噬而发生的(React 16 有更好的错误处理并防止这种情况发生)。当发生这种情况时,我很高兴查看更多案例,因此请随时将它们添加到 cmets 中。
  • 帮助!我并没有真正注意到有一个预加载器。
  • 这个答案对我很有帮助。我正在为一些空的“refs”引用而苦苦挣扎,结果发现“元素”根本没有被渲染。
【解决方案2】:

对问题的不同观察。

我发现这个问题只发生在开发模式下。 经过更多调查,我发现在我的 Webpack 配置中禁用 react-hot-loader 可以防止这个问题。

我正在使用

  • “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 崩溃(在 componentDidMount 中未定义 ref)

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

  inputRef (input) {
    this.inputField = input
  }

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

老实说,热重载通常很难“正确”。随着开发工具的快速更新,每个项目都有不同的配置。 也许我的特定配置可以修复。如果是这样,我会在这里告诉你。

【讨论】:

  • 这可以解释为什么我在 CodePen 中遇到了问题,但在我的情况下使用内联函数没有帮助。
【解决方案3】:

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

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

总是清除间隔,例如,

componentWillUnmount(){
    clearInterval(interval_holder)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-02
    • 2020-08-17
    • 1970-01-01
    • 1970-01-01
    • 2020-12-16
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    相关资源
    最近更新 更多