[解決済み] Reactで、オートサイズのDOM要素の幅に対応するにはどうしたらいいですか?
質問
私は React コンポーネントを使用した複雑な Web ページを持っており、ページを静的なレイアウトから、より応答性の高い、サイズ変更可能なレイアウトに変換しようとしています。しかし、私はReactの制限に遭遇し続け、これらの問題を処理するための標準的なパターンがあるかどうか疑問に思っています。私の特定のケースでは、div としてレンダリングされるコンポーネントがあり、display:table-cell と width:auto があります。
なぜなら、要素が実際に DOM に配置されない限り、要素のサイズを計算できないからです (実際のレンダリング幅を推論するための完全なコンテキストを持つ)。マウスの相対的な位置決めなどのためにこれを使用するほかに、コンポーネント内の SVG 要素に幅属性を適切に設定するためにも、これが必要です。
さらに、ウィンドウのサイズが変更されたとき、セットアップ中に 1 つのコンポーネントから別のコンポーネントにサイズの変更をどのように伝えるのでしょうか? サードパーティの SVG レンダリングをすべて shouldComponentUpdate で行っていますが、そのメソッド内で自分自身や他の子コンポーネントに状態やプロパティを設定することはできません。
Reactを使用してこの問題に対処する標準的な方法はありますか?
どのように解決するのですか?
最も現実的な解決策は、以下のようなライブラリを使用することです。 リアクトメジャー .
更新
: リサイズ検出用のカスタムフックが追加されました (個人的には試していませんが)。
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>
);
関連
-
[解決済み】ある要素が可視DOMに存在するかどうかを確認するにはどうすればいいですか?
-
[解決済み] JavaScriptで配列の先頭に新しい配列要素を追加するにはどうすればよいですか?
-
[解決済み] どのDOM要素にフォーカスがあるかを調べるには?
-
[解決済み] jQueryで名前を指定して要素を選択するには?
-
[解決済み] DOM要素が現在のビューポートで表示されているかどうかを確認するにはどうすればよいですか?
-
[解決済み] Reactで親の状態を更新するにはどうしたらいいですか?
-
[解決済み】react router v4 / v5でネストされたルート
-
[解決済み] 文字列のn番目の出現箇所を取得するには?
-
[解決済み] Javascript の parseInt() で先頭のゼロを削除する。
-
[解決済み] これは純関数ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] AngularJSのエラーです。Cross Origin リクエストはプロトコルスキーム http, data, chrome-extension, https に対してのみサポートされています。
-
[解決済み] ジェスト あるクラスの特定のメソッドをモックする方法
-
[解決済み] 配列からオブジェクトを生成する
-
[解決済み] チェックボックスが選択されているかどうかを確認するjQuery
-
[解決済み] コールバック地獄とは何か、RXはそれをどのように、そしてなぜ解決するのか?
-
[解決済み] JavaScript のオブジェクトの配列を比較し、最小値/最大値を取得する
-
[解決済み] TypeScriptプロジェクトで既存のC#クラス定義を再利用する方法
-
[解決済み] 各オブジェクトに?重複
-
[解決済み] jQueryを使用して、すべてのクリックイベントハンドラを削除するにはどうすればよいですか?
-
[解決済み] Fetch: ステータスがOKでない場合、プロミスを拒否し、エラーをキャッチするか?