【问题标题】:A better way to write this React Class Component with Hooks?用 Hooks 编写这个 React 类组件的更好方法是什么?
【发布时间】:2020-07-23 17:25:14
【问题描述】:

我有一个固定高度的部分。我不知道组件何时安装(第一次渲染)进入的内容是否适合。如果它不适合,那么我需要呈现一个“阅读更多”按钮。

看起来像这样:

我最初使用生命周期方法 DidMount/DidUpdate 将其编写为 Class 组件:

类组件

import React, { createRef } from "react"
import styled from "@emotion/styled"

import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"

const StyledHeightContainer = styled.div`
  max-height: 150px;
  overflow: hidden;
`

class ParagraphList extends React.Component {
  state = {
    overflowActive: false,
  }
  wrapper = createRef() // so we can get a ref to the height container

  isOverflowing(el) {
    if (el) return el.offsetHeight < el.scrollHeight
  }

  componentDidMount() {
    this.setState({ overflowActive: this.isOverflowing(this.wrapper.current) })
  }

  componentDidUpdate() {
    if (this.wrapper.current && !this.state.overflowActive) {
      this.setState({
        overflowActive: this.isOverflowing(this.wrapper.current),
      })
    }
  }

  handleClick() {
    this.setState({ overflowActive: false })
  }

  render() {
    const { moreButtonText, titleText, paragraphs, theme } = this.props

    return (
      <>
        <Section overflowActive={this.state.overflowActive}>
          {this.state.overflowActive || !this.wrapper.current ? (
            <StyledHeightContainer ref={this.wrapper}>
              <Paragraphs paragraphs={paragraphs} />
            </StyledHeightContainer>
          ) : (
            <Paragraphs paragraphs={paragraphs} />
          )}
        </Section>
        {overflowActive ?
         <ButtonReadMore
           onClicked={handleClick.bind(this)}
           moreButtonText={moreButtonText}
           theme={theme}
         />
        : null}
      </>
    )
  }
}

export default ParagraphList

我解释流程的最佳方式:

    1234563
  1. componentDidMount -> 尝试设置溢出标志(这将是错误的,因为此时我们还没有完成渲染,所以引用将为空)。但是无论如何设置标志,我们都会排队一个额外的渲染通道

  2. 第一次初始渲染完成 -> 我们现在有一个 div 的引用

  3. 第二次(排队)渲染发生,触发componentDidUpdate -> 我们可以计算溢出并在内容溢出时将标志设置为true

  4. 当用户单击按钮时 -> 将标志设置为 false,这将触发重新渲染,因此 StyledHeightContainer 将从 DOM 中删除。

带钩子的功能组件

Sandbox of the code

当我使用 Hooks 将其重写为功能组件时,我最终得到了:

import React, { createRef, useEffect, useState } from "react"
import styled from "@emotion/styled"

import Section from "../Section"
import ButtonReadMore from "./ButtonReadMore"
import Paragraphs from "./Paragraphs"

const StyledHeightContainer = styled.div`
  max-height: 150px;
  overflow: hidden;
`

const ParagraphList = ({ moreButtonText, titleText, paragraphs, theme }) => {
  const [overflowActive, setOverflowActive] = useState(false)
  const [userClicked, setUserClicked] = useState(false)
  const wrapper = createRef(false) // so we can get a ref to the height container

  const isOverflowing = el => {
    if (el) return el.offsetHeight < el.scrollHeight
  }

  useEffect(() => {
    if (!userClicked && !overflowActive && wrapper.current) {
      setOverflowActive(isOverflowing(wrapper.current))
    }
  }, [userClicked]) // note: we only care about state change if user clicks 'Read More' button

  const handleClick = () => {
    setOverflowActive(false)
    setUserClicked(true)
  }

  return (
    <>
      <Section theme={theme} overflowActive={overflowActive}>
        {!userClicked && (overflowActive || !wrapper.current)  ? (
          <StyledHeightContainer ref={wrapper}>
            <Paragraphs paragraphs={paragraphs} />
          </StyledHeightContainer>
        ) : (
          <Paragraphs paragraphs={paragraphs} />
        )}
      </Section>
      {overflowActive ?
        <ButtonReadMore
          onClicked={handleClick.bind(null)}
          moreButtonText={moreButtonText}
          theme={theme}
        />
        : null}
    </>
  )
}

export default ParagraphList

我很惊讶我需要添加另一个状态 (userClicked),这是我强制进行第二次渲染的方式(即相当于类解决方案中的 componentDidUpdate)。

这是正确的还是有人可以看到更简洁的方法来编写第二个解决方案?

注意

我问的一个原因是因为在控制台中我收到了这个警告:

48:6  warning  React Hook useEffect has missing dependencies:
'overflowActive' and 'wrapper'. Either include them or remove the
dependency array  react-hooks/exhaustive-deps

而且我不 THINK 我想将它们添加到依赖数组中,因为我不想在它们更改时触发渲染...?

【问题讨论】:

  • 您能否为您的第二种方法创建一个代码沙箱 (codesandbox.io) 或其他东西?如果需要,我会更容易重构它。
  • 好的,我可以试试。给我一些时间,因为我以前没有为 React 做过这些
  • 这很简单,只需为此代码创建一个示例/模拟。这样我们就可以玩了:)
  • @ManishSundriyal 我添加了代码,但由于所有数据都是从 GraphQL 查询动态加载的,如果我不模拟所有数据,代码将无法在沙箱中运行
  • @ManishSundriyal 好的,让它在沙盒中工作:codesandbox.io/s/…

标签: javascript reactjs react-hooks


【解决方案1】:

我真的很喜欢解决问题。

这里是实现:https://codesandbox.io/s/react-using-hooks-in-section-component-5gibi?file=/src/ParagraphList.js

首先,我在想

useEffect(() => {
  setOverflowActive(isOverflowing(wrapper.current));
}, [wrapper]);

但如果我们这样做,它会再次调用 useEffect,就像我们单击 Read more 按钮时一样。因为它是在比较包装器的引用而不是它的值。

所以,为了避免引用比较,我们必须使用 useCallback 钩子。

 const isOverflowingNode = node => {
    return node.offsetHeight < node.scrollHeight;
  };

  const wrapper = useCallback(node => {
    if (node !== null) {
      setOverflowActive(isOverflowingNode(node));
    }
  }, []);

我遇到了漂亮的讨论:https://github.com/facebook/react/issues/14387

更多信息: https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

谢谢你的问题:)

【讨论】:

    【解决方案2】:

    您可以添加一个额外的useEffect(() =&gt; (...),[]),其作用类似于componentDidMount()。另一个useEffect(() =&gt; (...)) 的作用类似于componentDidUpdate()。然后你应该能够摆脱userClicked

    这是一个关于生活方式方法如何与钩子一起使用的很好的链接。 https://dev.to/trentyang/replace-lifecycle-with-hooks-in-react-3d4n

      useEffect(() => {
        setOverflowActive(isOverflowing(wrapper.current));
      }, []);
    
      useEffect(() => {
        if (!overflowActive && wrapper.current) {
          setOverflowActive(isOverflowing(wrapper.current))
        }
      });
    

    如果您希望在布局之后进行更新,则第二个可能需要为 useLayoutEffect

    【讨论】:

    • 谢谢,好主意,但遗憾的是这不起作用(即使使用 useLayoutEffect) - 控制台警告无限事件链:(
    猜你喜欢
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-01
    相关资源
    最近更新 更多