Лучший способ написать этот компонент класса React с помощью хуков? - PullRequest
2 голосов
/ 11 апреля 2020

У меня есть секция с фиксированной высотой. Я не знаю, когда компонент монтируется (сначала рендерится), подойдет ли входящий контент или нет. Если он НЕ подходит, тогда мне нужно отобразить кнопку «Подробнее».

Это выглядит так: enter image description here

Первоначально я написал это как Компонент класса с использованием методов жизненного цикла DidMount / DidUpdate:

Компонент класса

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. Когда компонент монтируется, флаг ложен, и у нас нет ссылки на div, поэтому StyledHeightContainer попытается отрендерить и, таким образом, предоставить ссылку на него

  2. В componentDidMount -> попытаться установите флаг переполнения (который будет ложным, потому что на этом этапе мы еще не завершили рендеринг, поэтому ссылка будет нулевой). Но, в любом случае, установив флаг, мы ставим в очередь дополнительный проход рендеринга

  3. Первый начальный рендеринг завершается -> теперь у нас есть ссылка на div

  4. 2-й (в очереди) рендеринг происходит, запуская 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

Я был удивлен, что мне нужно было добавьте еще одно состояние (userClicked), как я вынуждаю 2-й рендеринг (ie. эквивалент componentDidUpdate в решении класса).

Это правильно, или кто-то может увидеть более краткий способ написать 2-е решение?

ПРИМЕЧАНИЕ

O одна из причин, которые я спрашиваю, заключается в том, что в консоли я получаю это предупреждение:

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 Я хочу добавить их в массив зависимостей, так как я не хочу запускать рендеринг при их изменении ...?

Ответы [ 2 ]

1 голос
/ 11 апреля 2020

Мне очень понравилось решение вопроса.

Вот реализация: https://codesandbox.io/s/react-using-hooks-in-section-component-5gibi?file= / src / ParagraphList. js

Прежде всего, я Я думал о

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

Но если мы сделаем это, он снова вызовет useEffect, как когда мы нажмем кнопку Подробнее. Потому что он сравнивал ссылку на обертку, а не ее значение.

Итак, чтобы избежать сравнения ссылок, мы должны использовать хук 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

Спасибо за вопрос:)

0 голосов
/ 11 апреля 2020

Вы можете добавить дополнительные 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, если вы хотите, чтобы обновление произошло после макета.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...