componentDidMount 调用 BEFORE ref 回调

IT技术 javascript reactjs
2021-02-11 05:16:21

问题

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

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

然后在componentDidMountDOM 引用中没有设置

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

我的理解是ref回调应该在挂载期间运行,但是添加console.log语句显示componentDidMountref 回调函数之前调用的。

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

那么 componentDidMount 是在所有 ref 回调都执行后被触发的吗?

是的。

我正在使用react15.4.1

我尝试过的其他东西

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

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

然后在 render

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

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

3个回答

简答:

React 保证在 refscomponentDidMountcomponentDidUpdatehooks之前设置但仅适用于实际渲染的儿童

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 只会为您实际从 render 返回的元素调用 ref 回调

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

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首先看到attached,然后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 元素没有被创建


如果将 Refs 传递给嵌套,则它们不会在生命周期之前设置 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用来存储 refs

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


仍然是一个问题?

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

我也对我的答案进行了编辑以解释这种情况。见第一节。希望这可以帮助!
2021-03-26 05:16:21
我认为在很多情况下这很可能是由于误解造成的。在 React 15 中,这也可能由于被吞掉的错误而发生(React 16 具有更好的错误处理并防止了这种情况)。我很高兴在发生这种情况时查看更多案例,因此请随时在评论中添加它们。
2021-03-28 05:16:21
嗨@DanAbramov 谢谢你!不幸的是,当我第一次遇到它时,我无法开发一个可重现的案例。可悲的是,我不再从事该项目,从那时起就无法复制。不过,这个问题已经变得足够流行,我同意,试图找到可重现的案例是关键,因为许多人似乎都遇到了这个问题。
2021-04-03 05:16:21
有帮助!我真的没有注意到有一个预加载器。
2021-04-07 05:16:21
这个答案真的帮助了我。我正在为一些空的“参考”参考而苦苦挣扎,好吧,结果“元素”根本没有被渲染。
2021-04-07 05:16:21

对问题的不同观察。

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

我在用

  • “react热加载器”:“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 中未定义引用)

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

  inputRef (input) {
    this.inputField = input
  }

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

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

这可能解释了为什么我在 CodePen 中遇到了这个问题,但是在我的情况下使用内联函数并没有帮助。
2021-04-14 05:16:21

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

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

总是清除间隔,例如,

componentWillUnmount(){
    clearInterval(interval_holder)
}