【问题标题】:How do you get the height of a stateless functional component that's a child?你如何获得一个无状态功能组件的高度?
【发布时间】:2018-01-13 12:12:24
【问题描述】:

我正在尝试获取无状态子组件的高度,以便能够在父类中使用它的高度,但出现以下错误:Invariant Violation: Stateless function components cannot have refs.

简化代码

父类

class App extends React.Component {
  componentDidMount() {
    console.log(this.header);
  }
  render() {
    return (
      <Child ref={component => this.header = component} />
    )
  }
}

孩子

const Child = () => (
  <header ref="header">
    Some text  
  </header>
)

有没有办法做到这一点?

这是a Codepen with the error的链接。

更新:

实际代码/上下文

所以我目前有一个 Header 组件,看起来像这样:

export const Header = ({dateDetailsButton, title, logo, backButton, signUpButton, inboxButton, header}) => (
    <header header className="flex flex-row tc pa3 bb b--light-gray relative min-h-35 max-h-35">
      {signUpButton ? <Link to="/edit-profile-contact" href="#" className="flex z-2"><AddUser /></Link> : null }
      {backButton ? <BackButton className="flex z-2" /> : null }
      <h1 className={logo ? "w-100 tc dark-gray lh-solid f4 fw5 tk-reklame-script lh-solid ma0" : "w-100 tc dark-gray lh-solid f4 fw4 absolute left-0 right-0 top-0 bottom-0 maa h1-5 z-0"}>{title}</h1>
      {dateDetailsButton ? <Link to="/date-details" className="absolute link dark-gray right-1 top-0 bottom-0 maa h1-5 z-2">Details</Link> : null }
      {inboxButton ? <Link to="/inbox" href="#" className="flex mla z-2"><SpeechBubble /></Link> : null}
    </header>
)

在某些情况下,我想向此 Header 添加逻辑以对其进行动画处理(例如,在用户滚动时的主页上,我正在为 Header 设置动画以在他们滚动经过某个点时变为固定 - 一个粘性标题,如果你愿意)。

我以前这样做的方法是只为具有特定功能(如上所述)的标头创建一个单独的类,而一个没有。但是为了保持我的代码干燥,我已经将 Header 分离为它自己的功能性无状态组件,以便将其包装在 Class 中,从而为其提供粘性标题功能。

下面是这个类:

export default class FeedHeader extends React.Component {

    constructor(props) {
        super(props);
        this.handleScroll = this.handleScroll.bind(this);
        this.state = {
            scrolledPastHeader: null,
            from: 0,
            to: 1,
        }
    }

    componentDidMount() {
        window.addEventListener('scroll', this.handleScroll);
        this.setState({navHeight: this.navHeight.offsetHeight});
    }

    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleScroll);
    }

    handleScroll(e) {
        const { navHeight, scrolledPastHeader } = this.state;
        const wy = document.body.scrollTop;
        if (wy < navHeight) this.setState({scrolledPastHeader: null})
        else if (wy > navHeight && wy < 100) {
            this.setState({scrolledPastHeader: false})
        }
        else this.setState({scrolledPastHeader: true})
    }

    getMotionProps() {
        const { scrolledPastHeader } = this.state;

        return scrolledPastHeader === false ?
        {
            style: {
                value: this.state.from
            }
        }
        : {
            style: {
                value: spring(this.state.to)
            }
        }
    }

    render() {
        const { brand, blurb } = this.props;
        const { scrolledPastHeader } = this.state;
        return (
            <Motion {...this.getMotionProps()}>
                {({value}) => {
                    return (
                        <Header ref="child" title={this.props.title} backButton={false} signUpButton inboxButton logo/>
                    );
                }}
            </Motion>
        )
    }
}

所以这是这个特定问题的背景 - 我希望这能让事情更清楚一点。

P.s 抱歉缺少上下文,我想答案会比看起来更直接!

【问题讨论】:

  • 从子组件&lt;header ref="header"&gt;中删除ref="header"
  • npm i react-dimensions@Dimensions 有什么问题,因为 HOC 将为您提供 containerWidthcontainerHeight 道具。
  • 我已经更新了 OP 以提供上下文。 react-dimensions 看起来不起作用,因为我需要访问子组件的高度,然后需要通过父组件访问。
  • 所以创建一个新组件,它使用 react-dimensions 作为外部元素来托管关心的那个。将道具暴露给孩子和父母,在渲染之前重新组合孩子。它不应该是自制解决方案的原因是您必须满足各种需求 - 滚动、调整事件大小、获取正确的文档元素等,才能更好地使经过测试的解决方案发挥作用。

标签: reactjs components stateless refs


【解决方案1】:

这是一个使用 React-waypoint 的解决方案。你可以在这里查看: https://codesandbox.io/s/qp3ly687

我为 onEnter 和 onLeave 添加了去抖动功能,这样它就不会像疯了一样更新状态,这样我们就可以优化滚动时的性能并且它不会锁定应用程序。同样,这是一个非常粗略的想法,您可以做什么,并且有很多选项可以改进实施。

=== 上一个

这是一种通过保留无状态组件来解决问题的方法。由于没有进一步解释上下文,这应该让您有机会了解如何获取位于无状态组件内的子组件的高度。

class App extends React.Component {
  componentDidMount() {
    console.log(this.child.offsetHeight);
  }
  render() {
    return (
      <div>
        <Wrapper>
          <div 
            ref={child => this.child = child}
            style={{height: 500}}>
            Some text!!
          </div>
         </Wrapper>
       </div>
    )
  }
}

const Wrapper = ({children}) => {
  return (
    <div>
      <header>Header</header>
      <div>{children}</div>
      <footer>Footer</footer>
    </div>
  )
}

ReactDOM.render(
  <App />,
  document.getElementById("main")
);

【讨论】:

  • 谢谢,我想我会更新 OP 以更好地理解上下文。感谢您的回答。
  • @A7DC 确实很棘手,您是否考虑过类似 React-Waypoints 的东西?而不是试图计算已经滚动过去以激活粘性模式的高度?我认为这是可以避免使用 refs 的情况之一。
  • @A7DC 很容易对航路点感到困惑,但它可以让你做同样的事情。它的工作原理是在标题组件之后放置一个元素,然后一旦您滚动过去元素(当它到达顶部时),您可以触发一个事件,该事件可以是一个状态更改,将一个类(例如固定)应用于您的标题。
  • @A7DC 我会尝试为您找到解决方案,就像我不久前所做的那样。
  • @A7DC 我刚刚更新了我的答案,以大致了解如何使用 react-waypoint 来做到这一点:)。这有点难听,但我相信还有很多改进空间。
【解决方案2】:

好的,首先,感谢大家的回复 - 非常感谢!

我开始按照 Win 的建议实现 react-waypoints,尽管很明显我需要更多地修改我的代码才能使其正常工作,所以我想我会进行最后一次搜索以尝试找到替代方案使用refs的解决方案。

我一直在寻找的答案其实很简单,来自Github issue threadmnpenner

解决方案

因为 React 不允许在无状态功能组件上使用 refs,所以您可以将功能组件包装在包装器中,同时为包装器提供自己的 ref,如下所示:

  render() {
    return (
      <div ref={el => {this.childWrap = el;}}>
        <Child />
      </div>
    )
  }

然后您可以通过定位包装器来访问组件的高度:

  componentDidMount() {
        if(this.childWrap && this.childWrap.firstChild) {
        let childHeight = this.childWrap.offsetHeight;
                console.log(childHeight);
    }
  }

虽然这可能不是最优雅的解决方案,但它确实暂时解决了我的问题。

这里是a Codepen 供参考。

【讨论】:

  • 呃,应该有更好的办法。必须有一种方法来获取对功能组件的 dom 元素的引用。
猜你喜欢
  • 2021-02-16
  • 1970-01-01
  • 2015-11-15
  • 2020-12-27
  • 1970-01-01
  • 2018-09-29
  • 2020-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多