Как я могу реагировать на ширину элемента DOM с автоматическим размером в React?

У меня сложная веб-страница с использованием компонентов React, и я пытаюсь преобразовать страницу из статического макета в более отзывчивый макет с изменяемым размером. Однако я продолжаю сталкиваться с ограничениями в React, и мне интересно, есть ли стандартный шаблон для решения этих проблем. В моем конкретном случае у меня есть компонент, который отображается как div с display: table-cell и width: auto.

К сожалению, я не могу запросить ширину моего компонента, потому что вы не можете вычислить размер элемента, если он фактически не помещен в DOM (который имеет полный контекст, с помощью которого можно определить фактическую ширину визуализации). Помимо использования этого для таких вещей, как относительное позиционирование мыши, мне также нужно это, чтобы правильно установить атрибуты ширины для элементов SVG в компоненте.

Кроме того, при изменении размера окна, как мне сообщить об изменении размера от одного компонента к другому во время установки? Мы выполняем всю нашу стороннюю визуализацию SVG в shouldComponentUpdate, но вы не можете устанавливать состояние или свойства для себя или других дочерних компонентов в этом методе.

Есть ли стандартный способ решения этой проблемы с помощью React?


person Steve Hollasch    schedule 18.08.2014    source источник
comment
кстати, вы уверены, что shouldComponentUpdate - лучшее место для рендеринга SVG? Похоже, что вам нужно componentWillReceiveProps или componentWillUpdate, если не render.   -  person Andy    schedule 21.11.2015
comment
Это может быть или не то, что вы ищете, но для этого есть отличная библиотека: github.com/ bvaughn / react-virtualized Взгляните на компонент AutoSizer. Он автоматически управляет шириной и / или высотой, поэтому вам не нужно.   -  person Maggie    schedule 24.06.2016
comment
@Maggie также ознакомьтесь с github.com/souporserious/react-measure, это отдельная библиотека для с этой целью и не будет помещать другие неиспользуемые вещи в ваш клиентский пакет.   -  person Andy    schedule 25.10.2016
comment
эй, я ответил на аналогичный вопрос здесь Это как-то другой подход, и он позволяет вам решать, что отображать в зависимости от типа экрана ( мобильный, планшет, компьютер)   -  person Calin ortan    schedule 26.07.2017
comment
@Maggie Я могу ошибаться в этом, но я думаю, что Auto Sizer всегда пытается заполнить своего родителя, а не определять размер, который ребенок принял, чтобы соответствовать его содержимому. Оба полезны в немного разных ситуациях.   -  person Andy    schedule 31.10.2017


Ответы (4)


Наиболее практичным решением является использование для этого библиотеки, например react-measure .

Обновление: теперь есть настраиваемая ловушка для определения размера (которую я лично не пробовал): реагировать на изменение размера. Будучи кастомным хуком, он выглядит более удобным в использовании, чем 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>
);
person Andy    schedule 08.10.2015
comment
Один вопрос об этом подходе, который я еще не исследовал, заключается в том, мешает ли он SSR. Я еще не уверен, как лучше всего разобраться с этим делом. - person Andy; 29.10.2015
comment
отличное объяснение, спасибо за такую ​​тщательность :) re: SSR, здесь обсуждается isMounted(): facebook.github.io/react/blog/2015/12/16/ - person ptim; 01.03.2016
comment
@memeLab Я только что добавил код для красивого компонента-оболочки, который берет большую часть шаблона из-под ответа на изменения DOM, взгляните :) - person Andy; 20.04.2016
comment
@ptim я в любом случае переписал код, чтобы не надо было проверять isMounted() - person Andy; 03.03.2017
comment
Это кажется слишком сложным способом получить ширину компонента: / - person Philll_t; 15.04.2018
comment
@Philll_t что делает, react-measure или мой пример с нуля? - person Andy; 17.04.2018
comment
@ Энди, просто весь процесс в целом. Ваш пример выдающийся, мой комментарий больше касается того факта, что мы должны вызвать всю библиотеку, чтобы получить достойное измерение. - person Philll_t; 17.04.2018
comment
@Philll_t да, было бы неплохо, если бы DOM упростил это. Но поверьте мне, использование этой библиотеки избавит вас от проблем, даже если это не самый простой способ измерения. - person Andy; 17.04.2018
comment
@Philll_t Еще одна вещь, о которой заботятся библиотеки, - это использование ResizeObserver (или полифилла) для немедленного получения обновлений размера вашего кода. - person Andy; 18.04.2020

Я думаю, что вам нужен метод жизненного цикла 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" />
  }

});
person couchand    schedule 20.08.2014
comment
Будьте осторожны, offsetWidth в настоящее время не существует в Firefox. - person Christopher Chiche; 24.07.2015
comment
@ChristopherChiche Я не верю, что это правда. Какая у вас версия? По крайней мере, у меня это работает, и документация MDN, кажется, предполагает, что это можно предположить: developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/ - person couchand; 24.08.2015
comment
Возможно, я ошибаюсь, но в прошлый раз, когда я проверял, offsetWidth для элемента svg вернул значение undefined. - person Christopher Chiche; 24.08.2015
comment
Ну я буду, это неудобно. В любом случае мой пример, вероятно, был неудачным, поскольку вы все равно должны явно указать размер элемента svg. AFAIK для всего, что вы ищете, динамический размер вы, вероятно, можете положиться на offsetWidth. - person couchand; 24.08.2015
comment
Для тех, кто приходит сюда, используя React 0.14 или выше, .getDOMNode () больше не нужен: facebook.github.io/react/blog/2015/10/07/. - person Hamund; 11.11.2015
comment
@couchand Я не думаю, что OP хотел получить ширину элемента SVG, просто установите его на основе чего-то еще, чтобы вы могли пересмотреть свой ответ, чтобы использовать какой-то другой элемент. - person Andy; 21.11.2015
comment
обратите внимание, что вы обычно хотите повторно измерить размер, по крайней мере, при изменении размера окна, так как это вообще не будет реагировать. - person Andy; 22.04.2016
comment
Хотя этот метод может показаться самым простым (и больше всего похожим на jQuery) способом доступа к элементу, Facebook теперь говорит, что мы не рекомендуем его, потому что строковые ссылки имеют некоторые проблемы, считаются устаревшими и, вероятно, будут удалены в одном из будущих выпусков. . Если вы в настоящее время используете this.refs.textInput для доступа к ссылкам, мы рекомендуем вместо этого шаблон обратного вызова. Вы должны использовать функцию обратного вызова вместо строки ref. здесь - person ahaurat; 12.04.2017

В качестве альтернативы решению couchand вы можете использовать findDOMNode

var Container = React.createComponent({

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

  render: function () {
    <svg />
  }
});
person Lukasz Madon    schedule 03.05.2015
comment
Чтобы уточнить: в React ‹= 0.12.x используйте component.getDOMNode (), в React› = 0.13.x используйте React.findDOMNode () - person pxwise; 13.05.2015
comment
@pxwise Aaaaa и теперь для ссылок на элементы DOM вам даже не нужно использовать какую-либо функцию с React 0.14 :) - person Andy; 09.10.2015
comment
@pxwise, я думаю, сейчас ReactDOM.findDOMNode()? - person ivarni; 29.02.2016
comment
@pxwise Обратите внимание, что ссылки на настраиваемые (определяемые пользователем) компоненты работают точно так же, как и раньше; только встроенные компоненты DOM затронуты этим изменением. - person Lukasz Madon; 29.02.2016
comment
Я так хотел бы прочитать это, прежде чем тратить столько времени на попытки заставить @Endy ответить на работу в ES6. Три строчки кода вместо всего того, что, я уверен, великолепно, но проходит мне через голову на второй неделе моей работы с React и ES6. ☺ - person Michael Scheper; 16.09.2016
comment
@MichaelScheper, конечно, но на самом деле он не реагирует на изменения ширины элемента, он проверяет размер только при монтировании. Я упрощу свой ответ, порекомендовав для этого отличную библиотеку ... - person Andy; 17.09.2016
comment
@Andy: Справедливо, и рекомендация библиотеки звучит хорошо. Я думаю, что часть вашего кода - это ES7? Это было бы достаточно справедливо, но если можно добавить пару слов для таких людей, как я, которые только начинают работать с React и ES6 / ES7, это может помочь и другим. - person Michael Scheper; 17.09.2016
comment
@MichaelScheper Это правда, в моем коде есть какой-то ES7. В моем обновленном ответе react-measure демонстрация (я думаю) чистый ES6. Трудно начинать, конечно ... Я прошел через то же безумие за последние полтора года :) - person Andy; 17.09.2016
comment
@MichaelScheper, кстати, вы можете найти здесь полезное руководство: github.com/gaearon/react- заставляет-тебя-грустить - person Andy; 17.09.2016

Вы можете использовать библиотеку 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

Он использует оптимизированный алгоритм на основе прокрутки / объектов, который я позаимствовал у людей, гораздо более умных, чем я. :)

person ctrlplusb    schedule 14.04.2016
comment
Хорошо, спасибо, что поделились. Я собирался создать репозиторий для своего собственного решения для «DimensionProvidingHoC». Но теперь я попробую. - person larrydahooster; 15.04.2016
comment
Буду признателен за любые отзывы, положительные или отрицательные :-) - person ctrlplusb; 15.04.2016
comment
Спасибо тебе за это. ‹Recharts /› требует, чтобы вы явно указали ширину и высоту, так что это было очень полезно - person JP DeVries; 06.06.2017