Як я можу реагувати на ширину елемента автоматичного розміру DOM у React?


86

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

На жаль, я не можу запитати ширину мого компонента, оскільки ви не можете обчислити розмір елемента, якщо він насправді не розміщений у DOM (який має повний контекст, з яким можна визначити фактичну відтворену ширину). Окрім використання цього для таких речей, як відносне позиціонування миші, мені це також потрібно для правильного встановлення атрибутів ширини для елементів SVG у складі компонента.

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

Чи існує стандартний спосіб вирішення цієї проблеми за допомогою React?


1
до речі, ти впевнений shouldComponentUpdate, що найкраще місце для відтворення SVG? Це схоже на те, що ти хочеш - це componentWillReceivePropsчи componentWillUpdateні render.
Енді,

1
Це може бути не тим, що ви шукаєте, але для цього існує чудова бібліотека: github.com/bvaughn/react-virtualized Подивіться на компонент AutoSizer. Він автоматично управляє шириною та / або висотою, тому вам не потрібно.
Меггі

@Maggie також перевірте github.com/souporserious/react-measure , це окрема бібліотека для цієї мети, і вона не буде вкладати інші невикористані речі у ваш пакет клієнтів.
Енді,

привіт, я відповів на подібне запитання тут. Це якось інший підхід, і це дозволить вам вирішити, що робити, залежно від вашого типу scren (мобільний телефон, планшет, настільний комп'ютер)
Calin ortan

@Maggie, я можу помилитися з цим, але я думаю, що Auto Sizer завжди намагається заповнити свого батька, а не виявляти розмір, який дитина прийняла відповідно до його вмісту. Обидва вони корисні в дещо різних ситуаціях
Енді,

Відповіді:


65

Найбільш практичним рішенням є використання бібліотеки для такого типу реакції .

Оновлення : тепер існує спеціальний хук для виявлення розміру (який я не пробував особисто): реагуйте-змінюйте розмір . Будучи спеціальним гачком, він виглядає зручнішим у використанні, ніж 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>
);

Одне питання щодо цього підходу, який я ще не досліджував, полягає в тому, чи перешкоджає він РСР. Я ще не впевнений, яким найкращим способом буде розглядати цю справу.
Енді,

чудове пояснення, дякую за настільки ретельну :) re: РСР, isMounted()тут обговорення : facebook.github.io/react/blog/2015/12/16/…
ptim

1
@memeLab Я щойно додав код для симпатичного компонента-обгортки, який виводить більшу частину шаблону з реакції на зміни DOM, подивіться :)
Енді,

1
@Philll_t так, було б непогано, якби DOM спростив це. Але повірте, використання цієї бібліотеки позбавить вас від проблем, хоча це не найпростіший спосіб отримати вимірювання.
Енді,

1
@Philll_t ще одна справа, про яку піклуються бібліотеки, - це використання ResizeObserver(або полізаповнення) для негайного оновлення розміру вашого коду.
Енді,

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

@ChristopherChiche Я не вірю, що це правда. Яку версію ви використовуєте? Це принаймні для мене працює, і, здається, документація MDN передбачає, що можна припустити: developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
couchand

1
Ну я буду, це незручно. У будь-якому випадку мій приклад, мабуть, був поганим, оскільки ви svgвсе одно повинні чітко розмірити елемент. AFAIK на все, на що ви б шукали динамічний розмір, на який ви, мабуть, можете покластися offsetWidth.
куван

3
Для тих, хто приходить сюди за допомогою React 0.14 або вище, .getDOMNode () більше не потрібен: facebook.github.io/react/blog/2015/10/07/…
Хамунд,

2
Хоча цей метод може здаватися найпростішим (і найбільш схожим на jQuery) способом доступу до елемента, Facebook зараз каже: "Ми не рекомендуємо його використовувати, оскільки посилання на рядки мають деякі проблеми, вважаються застарілими і, можливо, будуть видалені в майбутньому Якщо ви зараз використовуєте this.refs.textInput для доступу до посилань, натомість ми рекомендуємо шаблон зворотного виклику ". Вам слід використовувати функцію зворотного виклику замість рядка ref. Інформація тут
ahaurat

22

В якості альтернативи рішенням кухні ви можете використовувати 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 Aaaaa, а тепер для посилань на елемент DOM вам навіть не потрібно використовувати жодну функцію з React 0.14 :)
Енді

5
@pxwise я вважаю, що ReactDOM.findDOMNode()зараз?
ivarni

1
@MichaelScheper Це правда, у моєму коді є ES7. У моїй оновленій відповіді react-measureдемонстрація (я думаю) чиста ES6. Починати це важко, точно ... Я пройшов те саме божевілля протягом останніх півтора року :)
Енді

2
@MichaelScheper до речі, деякі корисні вказівки ви можете знайти тут: github.com/gaearon/react-makes-you-sad
Енді,

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.