source

렌더링 전에 리액트 컴포넌트의 크기(높이/폭)를 확인하는 방법

gigabyte 2023. 2. 14. 21:26
반응형

렌더링 전에 리액트 컴포넌트의 크기(높이/폭)를 확인하는 방법

스스로 렌더링하기 전에 치수를 미리 알아야 하는 반응 컴포넌트가 있습니다.

는 jquery로 만들 수 .$('#container').width()컴포넌트를 만들 때 컨테이너의 폭을 미리 파악합니다.

<div id='container'></div>

이 용기의 치수는 페이지의 다른 용기와 함께 CSS에 정의됩니다.React에서 구성 요소의 높이, 폭 및 배치를 정의하는 사람은 누구입니까?나는 CSS가 그것을 하고 그것에 접근할 수 있는 것에 익숙하다.그러나 React에서는 컴포넌트가 렌더링된 후에만 해당 정보에 액세스할 수 있는 것 같습니다.

다음 예제에서는 react hook useEffect 사용합니다.

여기서의 작업 예

import React, { useRef, useLayoutEffect, useState } from "react";

const ComponentWithDimensions = props => {
  const targetRef = useRef();
  const [dimensions, setDimensions] = useState({ width:0, height: 0 });

  useLayoutEffect(() => {
    if (targetRef.current) {
      setDimensions({
        width: targetRef.current.offsetWidth,
        height: targetRef.current.offsetHeight
      });
    }
  }, []);

  return (
    <div ref={targetRef}>
      <p>{dimensions.width}</p>
      <p>{dimensions.height}</p>
    </div>
  );
};

export default ComponentWithDimensions;

몇 가지 주의사항

useEffect는 폭과 높이에 대한 자신의 영향을 검출할 수 없습니다.

하지 않고 를 들어, 「」는 다음과 같습니다.const [dimensions, setDimensions] = useState({});시 됩니다.

  • css를 통해 컴포넌트에 명시적인 높이가 설정되지 않았습니다.
  • useEffect를 사용하여 너비와 높이를 측정할 수 있는 것은 useEffect를 사용할 수 있습니다.
  • 구성 요소 내용은 높이 및 너비 변수가 있는 p 태그뿐입니다. 비어 있으면 구성 요소의 높이가 0이 됩니다.
  • 새 상태 변수를 설정한 후 useEffect가 다시 실행되지 않습니다.

이것은 대부분의 사용 사례에서는 문제가 되지 않지만, 창 크기 조정에 영향을 미치기 때문에 포함하려고 합니다.

창 크기 조정

나는 또한 원래의 질문에는 탐구되지 않은 몇 가지 함의가 있다고 생각한다.차트 등 동적으로 그려진 컴포넌트의 윈도우 크기 조정 문제에 부딪혔습니다.

이 답변은 명기되어 있지 않지만 기재되어 있습니다.

  1. 응용 프로그램에서 치수가 필요한 경우 창 크기 조정 시 필요할 수 있습니다.
  2. 상태 또는 소품 변경만 다시 그릴 수 있으므로 치수 변경을 모니터링하려면 창 크기 조정 청취자도 필요합니다.
  3. 보다 복잡한 컴포넌트로 창 크기 조정 이벤트마다 컴포넌트를 다시 그리면 퍼포먼스가 향상됩니다.set Timeout과 clear를 도입했습니다.인터벌은 도움이 되었다.내 컴포넌트에 차트가 포함되어 있어서 CPU가 급증하고 브라우저가 크롤링하기 시작했다.아래 해결방법으로 이것을 수정했습니다.

아래의 코드, 여기서의 작업

import React, { useRef, useLayoutEffect, useState } from 'react';

const ComponentWithDimensions = (props) => {
    const targetRef = useRef();
    const [dimensions, setDimensions] = useState({});

    // holds the timer for setTimeout and clearInterval
    let movement_timer = null;

    // the number of ms the window size must stay the same size before the
    // dimension state variable is reset
    const RESET_TIMEOUT = 100;

    const test_dimensions = () => {
      // For some reason targetRef.current.getBoundingClientRect was not available
      // I found this worked for me, but unfortunately I can't find the
      // documentation to explain this experience
      if (targetRef.current) {
        setDimensions({
          width: targetRef.current.offsetWidth,
          height: targetRef.current.offsetHeight
        });
      }
    }

    // This sets the dimensions on the first render
    useLayoutEffect(() => {
      test_dimensions();
    }, []);

    // every time the window is resized, the timer is cleared and set again
    // the net effect is the component will only reset after the window size
    // is at rest for the duration set in RESET_TIMEOUT.  This prevents rapid
    // redrawing of the component for more complex components such as charts
    window.addEventListener('resize', ()=>{
      clearInterval(movement_timer);
      movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
    });

    return (
      <div ref={ targetRef }>
        <p>{ dimensions.width }</p>
        <p>{ dimensions.height }</p>
      </div>
    );
}

export default ComponentWithDimensions;

re: 크기 조정 타임아웃 - 이 값에서 다운스트림 차트를 사용하여 대시보드를 그리는데 100ms가RESET_TIMEOUTCPU 사용량과 응답성 사이에서 균형을 잘 잡은 것 같습니다.저는 어떤 것이 이상적인지에 대한 객관적인 데이터가 없기 때문에 이것을 변수로 삼았습니다.

이미 언급했듯이 DOM에 렌더링될 때까지 요소의 치수를 가져올 수 없습니다.할 수 하고 React에서 크기를입니다.componentDidMount이치노

는 모범을 보였다.

은 이쪽에서 사용한다는 점에 유의하시기 바랍니다.setStatecomponentDidMount안티 패턴이지만, 이 경우는 문제가 없습니다.그것이 바로 우리가 달성하려고 하는 것이기 때문입니다.

건배!

코드:

import React, { Component } from 'react';

export default class Example extends Component {
  state = {
    dimensions: null,
  };

  componentDidMount() {
    this.setState({
      dimensions: {
        width: this.container.offsetWidth,
        height: this.container.offsetHeight,
      },
    });
  }

  renderContent() {
    const { dimensions } = this.state;

    return (
      <div>
        width: {dimensions.width}
        <br />
        height: {dimensions.height}
      </div>
    );
  }

  render() {
    const { dimensions } = this.state;

    return (
      <div className="Hello" ref={el => (this.container = el)}>
        {dimensions && this.renderContent()}
      </div>
    );
  }
}

할 수 없어요.어쨌든 믿을 수 있는 건 아니야.이것은 브라우저 동작의 일반적인 제한이며, React가 아닙니다.

했을 때$('#container').width()DOM에 렌더링된 요소의 너비를 조회하고 있습니다.jQuery에서도 이 문제를 피할 수 없습니다.

요소를 렌더링하기 전에 반드시 요소의 폭이 필요한 경우 요소를 추정해야 합니다.표시하기 전에 측정해야 하는 경우 적용하면서 측정하면 됩니다.visibility: hidden또는 페이지 상의 어딘가로 이산적으로 렌더링한 후 측정 후 이동합니다.

창 크기 조정에 대한 @shane의 접근법에 예기치 않은 "gotcha"가 있습니다.기능 컴포넌트는 모든 리렌더에 새로운 이벤트청취자를 추가하며 이벤트청취자를 삭제하지 않으므로 이벤트청취자의 수는 크기 조정마다 기하급수적으로 증가합니다.각 콜을 window.addEventListener에 기록함으로써 확인할 수 있습니다.

window.addEventListener("resize", () => {
  console.log(`Resize: ${dimensions.width} x ${dimensions.height}`);
  clearInterval(movement_timer);
  movement_timer = setTimeout(test_dimensions, RESET_TIMEOUT);
});

이 문제는 이벤트 정리 패턴을 사용하여 해결할 수 있습니다.다음은 @shane의 코드와 이 튜토리얼이 혼합된 코드이며, 커스텀 훅에 크기 조정 로직이 포함되어 있습니다.

/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useLayoutEffect, useRef } from "react";

// Usage
function App() {
  const targetRef = useRef();
  const size = useDimensions(targetRef);

  return (
    <div ref={targetRef}>
      <p>{size.width}</p>
      <p>{size.height}</p>
    </div>
  );
}

// Hook
function useDimensions(targetRef) {
  const getDimensions = () => {
    return {
      width: targetRef.current ? targetRef.current.offsetWidth : 0,
      height: targetRef.current ? targetRef.current.offsetHeight : 0
    };
  };

  const [dimensions, setDimensions] = useState(getDimensions);

  const handleResize = () => {
    setDimensions(getDimensions());
  };

  useEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  useLayoutEffect(() => {
    handleResize();
  }, []);
  return dimensions;
}

export default App;

여기 작업 가 있습니다.

이 코드는 단순성을 위해 타이머를 사용하지 않지만 링크된 튜토리얼에서 이 접근법에 대해 설명합니다.

앞에서 설명한 바와 같이 브라우저의 제한은 DOM을 조작하는 스크립트 간 및 이벤트 핸들러 실행 간에 한 번에 "한 스레드 내" 렌더링하는 것입니다.DOM을 조작/로드한 후 치수를 얻으려면 양보(함수에서 탈퇴)하고 브라우저에 렌더링을 맡기고 렌더링이 완료된 이벤트에 반응해야 합니다.

하지만 다음 방법을 시도해 보세요.
CSS를 설정할 수 있습니다.display: hidden; position: absolute;원하는 폭을 얻기 위해 보이지 않는 경계 상자로 제한합니다.다음으로 렌더링 완료 후 콜을 실시합니다.$('#container').width().

아이디어는 다음과 같습니다.display: hidden요소가 표시되는 경우 필요한 공간을 차지하도록 합니다.연산은 백그라운드에서 수행해야 합니다.나는 그것이 "렌더하기 전"으로 적합하다고 확신할 수 없다.


면책사항:
아직 안 해봤으니까 잘 됐는지 알려주세요.
그리고 리액트랑 어떻게 섞일지 모르겠어요.

@Stanko의 솔루션은 훌륭하고 간결하지만, 렌터 후의 것입니다.다른 시나리오가 있는데<p>SVG 내의 요소<foreignObject>(리차트 차트에서).<p>줄바꿈 텍스트와 너비 구속의 최종 높이를 포함합니다.<p>예측하기 어렵다.<foreignObject>뷰포트는 기본적으로 뷰포트로 너무 길면 기본 SVG 요소에 대한 클릭/탭을 차단하고 너무 짧아서 아래쪽을 잘라냅니다.<p>리액트 렌더링 전에 DOM만의 스타일로 정해진 높이가 필요합니다.그리고 JQuery는 없습니다.

기능하는 React 컴포넌트에서 더미를 만듭니다.<p>노드를 문서의 클라이언트 뷰포트 외부에 있는 실시간 DOM에 배치하고 측정한 후 다시 제거합니다.그런 다음 이 측정을 사용하여<foreignObject>.

[CSS 수업을 이용한 방법으로 편집][편집:파이어폭스는 findCssClassBy를 싫어한다.셀렉터, 지금은 하드코딩으로 고정되어 있습니다.]

const findCssClassBySelector = selector => [...document.styleSheets].reduce((el, f) => {
  const peg = [...f.cssRules].find(ff => ff.selectorText === selector);
  if(peg) return peg; else return el;
}, null);

// find the class
const eventLabelStyle = findCssClassBySelector("p.event-label")

// get the width as a number, default 120
const eventLabelWidth = eventLabelStyle && eventLabelStyle.style ? parseInt(eventLabelStyle.style.width) : 120

const ALabel = props => {
  const {value, backgroundcolor: backgroundColor, bordercolor: borderColor, viewBox: {x, y}} = props

  // create a test DOM node, place it out of sight and measure its height
  const p = document.createElement("p");
  p.innerText = value;
  p.className = "event-label";
  // out of sight
  p.style.position = "absolute";
  p.style.top = "-1000px";
  // // place, measure, remove
  document.body.appendChild(p);
  const {offsetHeight: calcHeight} = p; // <<<< the prize
  // does the DOM reference to p die in garbage collection, or with local scope? :p

  document.body.removeChild(p);
  return <foreignObject {...props} x={x - eventLabelWidth / 2} y={y} style={{textAlign: "center"}} width={eventLabelWidth} height={calcHeight} className="event-label-wrapper">
    <p xmlns="http://www.w3.org/1999/xhtml"
       className="event-label"
       style={{
         color: adjustedTextColor(backgroundColor, 125),
         backgroundColor,
         borderColor,
       }}
    >
      {value}
    </p>
  </foreignObject>
}

추악하고, 많은 추측들이 있고, 아마 느리고, 쓰레기 때문에 불안하지만, 효과가 있어요.폭 받침은 숫자여야 합니다.

스택 오버플로에서 찾은 모든 솔루션은 매우 느리거나 최신 React 규약에 맞지 않습니다.그러다가 우연히 발견했어

https://github.com/wellyshen/react-cool-dimensions

ResizeObserver를 사용하여 요소의 크기를 측정하고 응답성 구성 요소를 고성능으로 처리하는 React 후크입니다.

여기서 시도한 솔루션보다 훨씬 빠르고 동작도 좋습니다.

import { useState, useEffect } from 'react'

const useContainerDimensions = containerRef => {
  const getDimensions = () => ({
    width: containerRef.current.offsetWidth,
    height: containerRef.current.offsetHeight
  })

  const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const handleResize = () => {
      setDimensions(getDimensions())
    }

    let dimensionsTimeout = setTimeout(() => {
      if(containerRef.current) {
        setDimensions(getDimensions())
      }
    }, 100)

    window.addEventListener("resize", handleResize)

    return () => {
      clearTimeout(dimensionsTimeout)
      window.removeEventListener("resize", handleResize)
    }
  }, [containerRef])

  return dimensions
}

export default useContainerDimensions

useContainerDimensions 사용자 지정 후크를 사용할 수 있습니다.픽셀의 폭과 높이가 필요한 경우 clientWidth와 client를 사용할 수 있습니다.오프셋 너비 및 오프셋 대신 높이높이.

언급URL : https://stackoverflow.com/questions/49058890/how-to-get-a-react-components-size-height-width-before-render

반응형