componentDidMount调用BEFORE引用回调

时间:2017-05-19 16:23:04

标签: javascript reactjs

问题

我使用内联函数定义

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

然后在componentDidMount中未设置DOM引用

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

我的理解是应该在mount期间运行ref回调,但是添加console.log语句显示componentDidMount在之前称为回调函数。

我在github上查看的其他代码示例(例如this discussion表示相同的假设,componentDidMount应该在任何ref回调之后被称为render中定义,它甚至是stated in the conversation

  
    

所以在所有引用回调之后,componentDidMount被触发     被执行了吗?

  
     

我正在使用react 15.4.1

我尝试过的其他事情

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

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

然后在render

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

此案例中的控制台记录显示回调确实在 componentDidMount之后被称为

4 个答案:

答案 0 :(得分:101)

简短回答:

React保证在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 没有设置的一些例子。

没有为未渲染的元素设置参考

React只会为实际从渲染返回的元素调用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附加,然后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没有任何内容可以设置引用,因为没有创建DOM元素

如果将生命周期传递给嵌套的ReactDOM.render()

Refs不会在生命周期之前设置

与上一节类似,如果您将带有ref的子项传递给另一个组件,则该组件可能会执行某些操作以防止及时附加ref。

例如,也许它不是从render()返回孩子,而是在生命周期钩子中调用ReactDOM.render()。您可以找到此here的示例。在该示例中,我们呈现:

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

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

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

render() {
  return null;
}

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

此问题的解决方案是使用 portals代替嵌套ReactDOM.render来电:

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

这样我们带有ref的<div>实际上包含在渲染输出中。

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

不要使用setState来存储引用

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

还是一个问题?

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

答案 1 :(得分:0)

对问题的不同观察。

我意识到问题只发生在开发模式下。 经过更多调查后,我发现在我的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}/>
    )
  }
}

说实话,热重装通常会成为“正确”的问题。使用dev工具快速更新,每个项目都有不同的配置。 也许我的特定配置可以修复。如果是这样的话,我会告诉你。

答案 2 :(得分:0)

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

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

总是清除间隔,例如

componentWillUnmount(){
    clearInterval(interval_holder)
}

答案 3 :(得分:-1)

在componentDidMount中,你能在浏览器DOM中找到你的ref元素(div.drawer)吗?如果没有,你就无法参考。由于问题出现在另一个更大的代码中,原因可能是没有呈现ref元素(div.drawer)。