React에서 자동 크기 조정 된 DOM 요소의 너비에 어떻게 반응 할 수 있습니까?


86

React 구성 요소를 사용하는 복잡한 웹 페이지가 있으며 페이지를 정적 레이아웃에서 더 반응이 빠르고 크기 조정이 가능한 레이아웃으로 변환하려고합니다. 그러나 저는 React에서 계속 한계에 부딪히며 이러한 문제를 처리하기위한 표준 패턴이 있는지 궁금합니다. 내 특정 경우에는 display : table-cell 및 width : auto를 사용하여 div로 렌더링되는 구성 요소가 있습니다.

안타깝게도 구성 요소의 너비를 쿼리 할 수 ​​없습니다. 실제로 DOM에 배치되지 않는 한 요소의 크기를 계산할 수 없기 때문입니다 (실제 렌더링 된 너비를 추론 할 전체 컨텍스트가 있음). 상대적인 마우스 위치와 같은 용도로 사용하는 것 외에도 구성 요소 내의 SVG 요소에 너비 속성을 올바르게 설정하려면이 기능이 필요합니다.

또한 창 크기가 조정될 때 설치 중에 한 구성 요소에서 다른 구성 요소로 크기 변경을 어떻게 전달합니까? shouldComponentUpdate에서 모든 타사 SVG 렌더링을 수행하고 있지만 해당 메서드 내에서 자신이나 다른 하위 구성 요소에 대한 상태 또는 속성을 설정할 수 없습니다.

React를 사용하여이 문제를 처리하는 표준 방법이 있습니까?


1
그런데 shouldComponentUpdateSVG를 렌더링하기에 가장 좋은 장소가 확실 합니까? 그것은 당신이 원하는 것이 componentWillReceiveProps거나 componentWillUpdate그렇지 않은 것처럼 들립니다 render.
Andy

1
이것은 당신이 찾고있는 것일 수도 있고 아닐 수도 있지만 이에 대한 훌륭한 라이브러리가 있습니다 : github.com/bvaughn/react-virtualized AutoSizer 구성 요소를 살펴보십시오. 너비 및 / 또는 높이를 자동으로 관리하므로 필요하지 않습니다.
Maggie

@Maggie는 github.com/souporserious/react-measure 도 확인하세요. 이 목적을위한 독립형 라이브러리이며 다른 사용하지 않는 항목을 클라이언트 번들에 넣지 않습니다.
Andy

헤이 나는 비슷한 질문에 대답 여기 그것은 어떻게 든 다른 접근 방식과는의 당신이 당신의 scren 유형 (모바일, 태블릿, 데스크톱)에 따라 렌더링할지 결정하자
칼린 ortan

@Maggie 나는 이것에 대해 틀릴 수 있지만 Auto Sizer는 항상 자녀가 콘텐츠에 맞게 크기를 감지하지 않고 부모를 채우려 고 시도한다고 생각합니다. 둘 다 약간 다른 상황에서 유용합니다
Andy

답변:


65

가장 실용적인 해결책은 react-measure 와 같은 라이브러리를 사용하는 입니다.

업데이트 : 이제 크기 감지를위한 사용자 지정 후크가 있습니다 (개인적으로 시도하지 않음) : react-resize-aware . 커스텀 훅이기 때문에 react-measure.

import * as React from 'react'
import Measure from 'react-measure'

const MeasuredComp = () => (
  <Measure bounds>
    {({ measureRef, contentRect: { bounds: { width }} }) => (
      <div ref={measureRef}>My width is {width}</div>
    )}
  </Measure>
)

구성 요소 간의 크기 변경을 전달하려면 onResize콜백을 전달하고 수신 한 값을 어딘가에 저장할 수 있습니다 (요즘 상태를 공유하는 표준 방법은 Redux 를 사용하는 것입니다 ).

import * as React from 'react'
import Measure from 'react-measure'
import { useSelector, useDispatch } from 'react-redux'
import { setMyCompWidth } from './actions' // some action that stores width in somewhere in redux state

export default function MyComp(props) {
  const width = useSelector(state => state.myCompWidth) 
  const dispatch = useDispatch()
  const handleResize = React.useCallback(
    (({ contentRect })) => dispatch(setMyCompWidth(contentRect.bounds.width)),
    [dispatch]
  )

  return (
    <Measure bounds onResize={handleResize}>
      {({ measureRef }) => (
        <div ref={measureRef}>MyComp width is {width}</div>
      )}
    </Measure>
  )
}

정말로 선호하는 경우 자신의 롤링 방법 :

DOM에서 값을 가져오고 창 크기 조정 이벤트 (또는에서 사용하는 구성 요소 크기 감지)를 수신하는 것을 처리하는 래퍼 구성 요소를 만듭니다 react-measure. DOM에서 가져올 소품을 지정하고 해당 소품을 자식으로 취하는 렌더링 기능을 제공합니다.

렌더링 한 내용은 DOM 소품을 읽기 전에 마운트해야합니다. 초기 렌더링 중에 해당 소품을 사용할 style={{visibility: 'hidden'}}수없는 경우 JS 계산 레이아웃을 가져 오기 전에 사용자가 볼 수 없도록 사용하는 것이 좋습니다.

// @flow

import React, {Component} from 'react';
import shallowEqual from 'shallowequal';
import throttle from 'lodash.throttle';

type DefaultProps = {
  component: ReactClass<any>,
};

type Props = {
  domProps?: Array<string>,
  computedStyleProps?: Array<string>,
  children: (state: State) => ?React.Element<any>,
  component: ReactClass<any>,
};

type State = {
  remeasure: () => void,
  computedStyle?: Object,
  [domProp: string]: any,
};

export default class Responsive extends Component<DefaultProps,Props,State> {
  static defaultProps = {
    component: 'div',
  };

  remeasure: () => void = throttle(() => {
    const {root} = this;
    if (!root) return;
    const {domProps, computedStyleProps} = this.props;
    const nextState: $Shape<State> = {};
    if (domProps) domProps.forEach(prop => nextState[prop] = root[prop]);
    if (computedStyleProps) {
      nextState.computedStyle = {};
      const computedStyle = getComputedStyle(root);
      computedStyleProps.forEach(prop => 
        nextState.computedStyle[prop] = computedStyle[prop]
      );
    }
    this.setState(nextState);
  }, 500);
  // put remeasure in state just so that it gets passed to child 
  // function along with computedStyle and domProps
  state: State = {remeasure: this.remeasure};
  root: ?Object;

  componentDidMount() {
    this.remeasure();
    this.remeasure.flush();
    window.addEventListener('resize', this.remeasure);
  }
  componentWillReceiveProps(nextProps: Props) {
    if (!shallowEqual(this.props.domProps, nextProps.domProps) || 
        !shallowEqual(this.props.computedStyleProps, nextProps.computedStyleProps)) {
      this.remeasure();
    }
  }
  componentWillUnmount() {
    this.remeasure.cancel();
    window.removeEventListener('resize', this.remeasure);
  }
  render(): ?React.Element<any> {
    const {props: {children, component: Comp}, state} = this;
    return <Comp ref={c => this.root = c} children={children(state)}/>;
  }
}

이를 통해 너비 변경에 응답하는 것은 매우 간단합니다.

function renderColumns(numColumns: number): React.Element<any> {
  ...
}
const responsiveView = (
  <Responsive domProps={['offsetWidth']}>
    {({offsetWidth}: {offsetWidth: number}): ?React.Element<any> => {
      if (!offsetWidth) return null;
      const numColumns = Math.max(1, Math.floor(offsetWidth / 200));
      return renderColumns(numColumns);
    }}
  </Responsive>
);

아직 조사하지 않은이 접근 방식에 대한 한 가지 질문은 SSR을 방해하는지 여부입니다. 그 사건을 처리하는 가장 좋은 방법이 무엇인지 아직 잘 모르겠습니다.
Andy

훌륭한 설명, 철저히 해주셔서 감사합니다. :) re : SSR, 여기에 대한 토론이 isMounted()있습니다 : facebook.github.io/react/blog/2015/12/16/…
ptim

1
@memeLab 방금 DOM 변경에 대한 응답에서 상용구의 대부분을 차지하는 멋진 래퍼 구성 요소에 대한 코드를 추가했습니다. 살펴보십시오. :)
Andy

1
@Philll_t 예, DOM이 이것을 더 쉽게 만들었다면 좋을 것입니다. 그러나 저를 믿으십시오.이 라이브러리를 사용하면 측정을 얻는 가장 기본적인 방법은 아니지만 문제를 해결할 수 있습니다.
Andy

1
@Philll_t 라이브러리가 처리하는 또 다른 일은 ResizeObserver코드에 대한 크기 업데이트를 즉시 가져 오기 위해 사용 (또는 폴리 필)하는 것입니다.
Andy

43

나는 당신이 찾고있는 수명주기 방법이 componentDidMount. 요소는 이미 DOM에 배치되었으며 구성 요소의 refs.

예를 들면 :

var Container = React.createComponent({

  componentDidMount: function () {
    // if using React < 0.14, use this.refs.svg.getDOMNode().offsetWidth
    var width = this.refs.svg.offsetWidth;
  },

  render: function () {
    <svg ref="svg" />
  }

});

1
offsetWidth현재 Firefox에 존재하지 않는 점에주의하세요 .
Christopher Chiche 2015

@ChristopherChiche 나는 그것이 사실이라고 믿지 않습니다. 어떤 버전을 실행하고 있습니까? 그것은 적어도 나를 위해 작동하고 MDN 문서는 그것이 가정 될 수 있다고 제안하는 것 같습니다 : developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
couchand

1
글쎄요, 그건 불편합니다. svg어쨌든 명시 적으로 요소의 크기를 지정해야하므로 내 예제는 아마도 좋지 않은 예제 일 것 입니다. AFAIK는 당신의 동적 크기를 찾고자하는 모든 것에 의지 할 수 있습니다 offsetWidth.
couchand 2015-08-24

3
React 0.14 이상을 사용하여 여기에 오는 사람에게는 .getDOMNode ()가 더 이상 필요하지 않습니다. facebook.github.io/react/blog/2015/10/07/…
Hamund

2
이 방법이 요소에 액세스하는 가장 쉬운 (그리고 가장 jQuery와 같은) 방법처럼 느껴질 수 있지만 이제 Facebook은 "문자열 참조에 문제가 있고 레거시로 간주되며 향후 제거 될 가능성이 있기 때문에 권장하지 않습니다. 현재 this.refs.textInput을 사용하여 참조에 액세스하는 경우 대신 콜백 패턴을 사용하는 것이 좋습니다. " 문자열 ref 대신 콜백 함수를 사용해야합니다. 여기 정보
ahaurat

22

couchand 솔루션 대신 findDOMNode를 사용할 수 있습니다.

var Container = React.createComponent({

  componentDidMount: function () {
    var width = React.findDOMNode(this).offsetWidth;
  },

  render: function () {
    <svg />
  }
});

10
명확히하기 위해 : React <= 0.12.x에서 component.getDOMNode () 사용, React> = 0.13.x에서 React.findDOMNode () 사용
pxwise

2
@pxwise Aaaaaand 이제 DOM 요소 참조를 위해 React 0.14와 함께 두 기능을 사용할 필요도 없습니다. :)
Andy

5
@pxwise 나는 그것이 ReactDOM.findDOMNode()지금 이라고 믿 습니까?
ivarni

1
@MichaelScheper 사실, 내 코드에 ES7이 있습니다. 업데이트 된 답변에서 react-measure데모는 순수한 ES6입니다. 시작하기는 어렵습니다. 확실히 ... 나는 지난 1 년 반 동안 같은 광기를 겪었습니다. :)
Andy

2
@MichaelScheper btw, 여기에서 유용한 지침을 찾을 수 있습니다. github.com/gaearon/react-makes-you-sad
Andy

6

내가 작성한 I 라이브러리를 사용하여 렌더링 된 구성 요소 크기를 모니터링하고 전달합니다.

예를 들면 :

import SizeMe from 'react-sizeme';

class MySVG extends Component {
  render() {
    // A size prop is passed into your component by my library.
    const { width, height } = this.props.size;

    return (
     <svg width="100" height="100">
        <circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
     </svg>
    );
  }
} 

// Wrap your component export with my library.
export default SizeMe()(MySVG);   

데모 : https://react-sizeme-example-esbefmsitg.now.sh/

Github : https://github.com/ctrlplusb/react-sizeme

그것은 내가 나보다 훨씬 영리한 사람들에게서 빌린 최적화 된 스크롤 / 객체 기반 알고리즘을 사용합니다. :)


공유해 주셔서 감사합니다. 'DimensionProvidingHoC'에 대한 사용자 지정 솔루션에 대한 리포지토리도 만들려고했습니다. 하지만 이제 이걸 한번해볼 게요.
larrydahooster

나는 어떤 피드백, 긍정적이거나 부정적인 :-) 감사하겠습니다
ctrlplusb

감사합니다. <Recharts />이 매우 도움이되었다 있도록 명시 적으로 폭과 높이를 설정하도록 요구
JP DeVries
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.