簡體   English   中英

用 Hooks 編寫這個 React Class 組件的更好方法是什么?

[英]A better way to write this React Class Component with Hooks?

我有一個固定高度的部分。 我不知道組件何時安裝(第一次渲染)進入的內容是否適合。 如果它不適合,那么我需要呈現一個“閱讀更多”按鈕。

它看起來像這樣: 在此處輸入圖像描述

我最初使用生命周期方法 DidMount/DidUpdate 將其編寫為 Class 組件:

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

我解釋流程的最佳方式:

  1. 當組件掛載時,標志為 false 並且我們沒有對 div 的引用,因此StyledHeightContainer將嘗試渲染並因此為其提供參考

  2. componentDidMount -> 嘗試設置溢出標志(這將是錯誤的,因為此時我們還沒有完成渲染,所以 ref 將為空)。 但是無論如何設置標志,我們排隊一個額外的渲染通道

  3. 第一次初始渲染完成 -> 我們現在有一個 div 的引用

  4. 第二次(排隊)渲染發生,觸發componentDidUpdate -> 我們可以計算溢出並在內容溢出時將標志設置為 true

  5. 當用戶單擊按鈕時 -> 將標志設置為 false,這將觸發重新渲染,因此StyledHeightContainer將從 DOM 中刪除。

帶鈎子的功能組件

代碼沙箱

當我使用 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

我很驚訝我需要添加另一個 state(userClicked),這是我強制進行第二次渲染的方式(即,相當於 class 解決方案中的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

而且我不想將它們添加到依賴項數組中,因為我不想在它們更改時觸發渲染...?

在解決查詢時我真的很享受。

這是實現: 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

謝謝你的問題:)

您可以添加一個額外的useEffect(() => (...),[]) ,其作用類似於componentDidMount() 還有另一個useEffect(() => (...)) ,其作用類似於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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM