web-dev-qa-db-ja.com

レンダリングする前に、反応コンポーネントのサイズ(高さ/幅)を取得する方法は?

私は事前にその寸法を知る必要がある反応コンポーネントがありますそれ自体がレンダリングされる前に

Jqueryでウィジェットを作成するときは、コンポーネントをビルドするときに、$('#container').width()を使用してコンテナーの幅を事前に取得することができます。

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

これらのコンテナの寸法は、ページ上の他の多数のコンテナとともにCSSで定義されています。 Reactのコンポーネントの高さ、幅、および配置を定義するのは誰ですか?私はCSSを使用してそれにアクセスできることに慣れています。しかし、Reactでは、コンポーネントがレンダリングされた後でのみその情報にアクセスできるようです。

27
foreyez

すでに述べたように、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>
    );
  }
}
7
Stanko

それはいけません。とにかく、確実ではありません。これは、Reactではなく、ブラウザの動作の制限です。

$('#container').width()を呼び出すと、DOMでレンダリングされたhasである要素の幅を照会します。 jQueryでも、これを回避することはできません。

その前に要素の幅が絶対に必要な場合rendersは、それを見積もる必要があります。 visibleになる前に測定する必要がある場合は、visibility: hiddenを適用するか、ページ上の個別の場所にレンダリングしてから測定後に移動することができます。

3
Litty

述べたように、それはブラウザーの制限です-DOMを操作するスクリプト間およびイベントハンドラーの実行間で(JSの観点から)一度に「1つのスレッドで」レンダリングされます。 DOMを操作またはロードした後に寸法を取得するには、yield(関数を残す)してブラウザーをレンダリングし、レンダリングが行われたイベントに反応する必要があります。

しかし、このトリックを試してください:
CSS _display: hidden; position: absolute;_を設定して、非表示の境界ボックスに制限し、希望の幅を取得することができます。次に、yieldし、レンダリングが完了したら、$('#container').width()を呼び出します。

アイデアは次のとおりです。_display: hidden_は、要素が表示された場合に必要なスペースを占めるため、計算はバックグラウンドで実行する必要があります。それが「レンダリング前」に該当するかどうかはわかりません。


免責事項:
まだ試していないので、うまくいったかどうかをお知らせください。
そして、それがどのようにReactと融合するかはわかりません。

0
Ondra Žižka

@Stankoのソリューションは簡潔で簡潔ですが、ポストレンダリングです。 SVG <p>内に<foreignObject>要素をレンダリングする別のシナリオがあります(Rechartsチャート内)。 <p>には折り返されるテキストが含まれており、幅に制約のある<p>の最終的な高さを予測するのは困難です。 <foreignObject>は基本的にはビューポートであり、長すぎると、基礎となるSVG要素へのクリック/タップがブロックされ、短すぎると<p>の下部が途切れます。 Reactレンダリングする前の、DOM独自のスタイルで決定される高さ。タイトなフィットが必要です。また、JQueryも必要ありません。

したがって、私の機能Reactコンポーネントで、ダミーの<p>ノードを作成し、それをドキュメントのクライアントビューポートの外側のライブDOMに配置し、測定して、もう一度削除します。次に、その測定値を使用します。 <foreignObject>の場合。

[CSSクラスを使用するメソッドで編集] [編集:Firefoxは今のところハードコーディングでスタックしているfindCssClassBySelectorを嫌っています。]

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>
}

醜い、多くの仮定、おそらく遅いと私はゴミについて緊張していますが、それは動作します。幅プロップは数値でなければならないことに注意してください。

0
dkloke

ウィンドウのサイズ変更を処理する@shaneのアプローチには、予期しない「問題」があります。機能コンポーネントは、すべての再レンダリングで新しいイベントリスナーを追加し、イベントリスナーを削除しないため、サイズ変更ごとにイベントリスナーの数が指数関数的に増加します。これは、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;

実際の例 here があります。

このコードでは、簡単にするためにタイマーを使用していませんが、そのアプローチについては、リンクされたチュートリアルで詳しく説明しています。

0
RobertJacobson