Reactで自動サイズ設定されたDOM要素の幅にどのように応答できますか?


86

Reactコンポーネントを使用する複雑なWebページがあり、ページを静的レイアウトからより応答性が高く、サイズ変更可能なレイアウトに変換しようとしています。しかし、私はReactで制限に直面し続けており、これらの問題を処理するための標準的なパターンがあるかどうか疑問に思っています。私の特定のケースでは、display:table-cellとwidth:autoを持つdivとしてレンダリングされるコンポーネントがあります。

残念ながら、コンポーネントの幅をクエリすることはできません。これは、要素が実際にDOM(実際にレンダリングされた幅を推定するための完全なコンテキストを持っている)に配置されない限り、要素のサイズを計算できないためです。これをマウスの相対的な配置などに使用するだけでなく、コンポーネント内のSVG要素に幅属性を適切に設定するためにも必要です。

さらに、ウィンドウのサイズが変更された場合、セットアップ中にあるコンポーネントから別のコンポーネントにサイズの変更を伝えるにはどうすればよいですか?サードパーティのSVGレンダリングはすべてshouldComponentUpdateで実行していますが、自分自身またはそのメソッド内の他の子コンポーネントに状態やプロパティを設定することはできません。

Reactを使用してこの問題に対処する標準的な方法はありますか?


1
ちなみに、shouldComponentUpdateSVGをレンダリングするのに最適な場所は確かですか?それはあなたが望むものであるcomponentWillReceivePropscomponentWillUpdateそうでないかのように聞こえますrender
アンディ

1
これはあなたが探しているものかもしれないし、そうでないかもしれませんが、これのための優れたライブラリがあります:github.com/bvaughn/react-virtualizedAutoSizer コンポーネントを見てください。幅や高さを自動的に管理するので、その必要はありません。
マギー

@Maggieはgithub.com/souporserious/react-measureもチェックしてください。これはこの目的のためのスタンドアロンライブラリであり、他の未使用のものをクライアントバンドルに入れません。
アンディ

ちょっと私はここで同様の質問に答えましたそれはどういうわけか異なるアプローチであり、あなたのスクリーンタイプ(モバイル、タブレット、デスクトップ)に応じて何をレンダリングするかを決めることができます
Calin ortan 2017

@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>
);

私がまだ調査していないこのアプローチについての1つの質問は、それがSSRに干渉するかどうかです。そのケースを処理するための最良の方法が何であるかはまだわかりません。
アンディ

偉大な説明なので、徹底しているの感謝:)再:SSRの議論がありますisMounted()。ここではfacebook.github.io/react/blog/2015/12/16/...
PTIM

1
@memeLab DOMの変更への応答からボイラープレートのほとんどを取り除く素敵なラッパーコンポーネントのコードを追加しました。ご覧ください:)
Andy

1
@Philll_tはい、DOMによってこれが簡単になればいいのですが。しかし、私を信じてください。このライブラリを使用すると、測定値を取得するための最も基本的な方法ではありませんが、問題を回避できます。
アンディ

1
@Philll_tライブラリが処理するもう1つのことは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 2015

@ChristopherChicheそれが本当だとは思わない。どのバージョンを実行していますか?それは、少なくとも私のために働き、MDNのドキュメントは、それが仮定できることを示唆しているようだ:developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/...
couchand

1
まあ、それは不便です。いずれにせよ、svg要素のサイズを明示的に指定する必要があるため、私の例はおそらく不十分なものでした。あなたがあなたのダイナミックなサイズを探しているであろうもののためにAFAIKはおそらく頼ることができますoffsetWidth
couchand 2015

3
誰もが使用してここに来るために0.14以上に反応して、.getDOMNode()はもはや必要ありません:facebook.github.io/react/blog/2015/10/07/...
Hamund

2
このメソッドは要素にアクセスする最も簡単な(そして最もjQueryのような)方法のように感じるかもしれませんが、Facebookは現在、「文字列参照にはいくつかの問題があり、レガシーと見なされ、将来的に削除される可能性があるため、これに反対することをお勧めしますリリース。現在this.refs.textInputを使用して参照にアクセスしている場合は、代わりにコールバックパターンをお勧めします。」文字列refの代わりにコールバック関数を使用する必要があります。ここに情報
ahaurat 2017

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 2015年

2
@pxwise Aaaaaそして今ではDOM要素の参照については、React0.14でどちらの関数も使用する必要はありません:)
Andy

5
@pxwise今だと思いますReactDOM.findDOMNode()か?
ivarni 2016

1
@MichaelScheper確かに、私のコードにはES7がいくつかあります。私の更新された回答では、react-measureデモンストレーションは(私が思うに)純粋なES6です。確かに、始めるのは難しいです...私は過去1年半にわたって同じ狂気を経験しました:)
アンディ

2
@MichaelScheperところで、ここでいくつかの有用なガイダンスを見つけることができます: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

それは私が私よりはるかに賢い人々から借りた最適化されたスクロール/オブジェクトベースのアルゴリズムを使用しています。:)


いいですね、共有してくれてありがとう。'DimensionPprovingHoC'のカスタムソリューションのリポジトリも作成しようとしていました。でも今からこれを試してみます。
larrydahooster 2016

正または負のフィードバックをいただければ
幸いです:

これありがとう。<Recharts />では、明示的な幅と高さを設定する必要があるため、これは非常に役立ちました
JP DeVries 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.