React'te otomatik olarak boyutlandırılan bir DOM öğesinin genişliğine nasıl yanıt verebilirim?


86

React bileşenlerini kullanan karmaşık bir web sayfam var ve sayfayı statik bir düzenden daha duyarlı, yeniden boyutlandırılabilir bir düzene dönüştürmeye çalışıyorum. Ancak, React ile ilgili sınırlamalarla karşılaşmaya devam ediyorum ve bu sorunların üstesinden gelmek için standart bir model olup olmadığını merak ediyorum. Benim özel durumumda, display: table-cell ve width: auto ile bir div olarak işlenen bir bileşene sahibim.

Ne yazık ki, bileşenimin genişliğini sorgulayamıyorum, çünkü gerçekten DOM'a yerleştirilmediği sürece bir öğenin boyutunu hesaplayamazsınız (gerçek oluşturulan genişliğin çıkarılacağı tam bağlama sahiptir). Bunu göreceli fare konumlandırma gibi şeyler için kullanmanın yanı sıra, bileşenin içindeki SVG öğelerinde genişlik özelliklerini doğru şekilde ayarlamak için buna ihtiyacım var.

Ek olarak, pencere yeniden boyutlandırıldığında, kurulum sırasında boyut değişikliklerini bir bileşenden diğerine nasıl iletebilirim? ShouldComponentUpdate içinde tüm 3. taraf SVG oluşturmayı yapıyoruz, ancak bu yöntemde kendiniz veya diğer alt bileşenler için durum veya özellikler ayarlayamazsınız.

React'i kullanarak bu problemi çözmenin standart bir yolu var mı?


1
bu arada, shouldComponentUpdateSVG'yi işlemek için en iyi yerin olduğundan emin misin ? Ne istediğini gibi geliyor componentWillReceivePropsya componentWillUpdatedeğilse render.
Andy

1
Aradığınız şey bu olabilir veya olmayabilir, ancak bunun için mükemmel bir kitaplık var : github.com/bvaughn/react-virtualized AutoSizer bileşenine bir göz atın. Genişliği ve / veya yüksekliği otomatik olarak yönetir, böylece gerek kalmaz.
Maggie

@Maggie github.com/souporserious/react-measure'a da göz atın , bu amaç için bağımsız bir kitaplıktır ve diğer kullanılmayan şeyleri müşteri paketinize koymaz .
Andy

hey benzer bir soru cevap burada Bir şekilde farklı bir yaklaşım ve en Eğer scren türü (mobil, tablet masaüstü) bağlı işlemek ne karar versin
Calin Ortan

@Maggie Bu konuda yanılıyor olabilirim, ancak Auto Sizer'ın çocuğun içeriğine uyması için aldığı boyutu algılamak yerine her zaman ebeveynini doldurmaya çalıştığını düşünüyorum. Her ikisi de biraz farklı durumlarda kullanışlıdır
Andy

Yanıtlar:


65

En pratik çözüm bunun için react-measure gibi bir kitaplık kullanmaktır .

Güncelleme : Artık yeniden boyutlandırma tespiti için özel bir kanca var (şahsen denemedim): react-yeniden boyutlandırmaya duyarlı . Özel bir kanca olduğundan, kullanmaktan daha uygun görünüyor 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>
)

Bileşenler arasında boyut değişikliklerini bildirmek için, bir onResize geri aramayı ve aldığı değerleri bir yerde saklayabilirsiniz (bugünlerde durumu paylaşmanın standart yolu Redux kullanmaktır ):

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

Aşağıdakileri gerçekten tercih ediyorsanız, kendiniz nasıl yuvarlayabilirsiniz:

DOM'dan değer almayı ve pencere yeniden boyutlandırma olaylarını dinlemeyi (veya bileşen yeniden boyutlandırma algılamasını kullanan) işleyen bir sarmalayıcı bileşeni oluşturun. react-measure ) . DOM'dan hangi sahne öğelerinin alınacağını söyleyin ve bu aksesuarları çocukken alan bir oluşturma işlevi sağlayın.

DOM props okunmadan önce oluşturduğunuz şeyin bağlanması gerekir; Bu aksesuarlar ilk oluşturma sırasında kullanılamadığında style={{visibility: 'hidden'}}, kullanıcının JS ile hesaplanan bir düzen almadan önce görmemesi için kullanmak isteyebilirsiniz .

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

Bununla genişlik değişikliklerine yanıt vermek çok basittir:

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

Bu yaklaşımla ilgili henüz araştırmadığım bir soru, SSR'ye müdahale edip etmediğidir. Bu davayı halletmenin en iyi yolunun ne olduğundan henüz emin değilim.
Andy

harika bir açıklama, bu kadar titiz olduğun için teşekkürler :) re: SSR, Burada bir tartışma isMounted()var: facebook.github.io/react/blog/2015/12/16/…
ptim

1
@memeLab DOM değişikliklerine yanıt vermenin çoğunu kullanan hoş bir sarmalayıcı bileşeni için kod ekledim, bir göz atın :)
Andy

1
@Philll_t evet, DOM bunu kolaylaştırırsa iyi olurdu. Ama güven bana, bu kitaplığı kullanmak, bir ölçüm almanın en basit yolu olmasa da sizi zahmetten kurtaracaktır.
Andy

1
@Philll_t Kitaplıkların ilgilendiği başka bir şey de ResizeObserverkodunuzda boyut güncellemelerini hemen almak için (veya bir çoklu doldurma ) kullanmaktır.
Andy

43

Bence aradığınız yaşam döngüsü yöntemi componentDidMount. Öğeler zaten DOM'a yerleştirilmiştir ve bunlar hakkında bileşenden bilgi alabilirsiniz refs.

Örneğin:

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Şu anda Firefox'ta bulunmadığına dikkat edin .
Christopher Chiche

@ChristopherChiche Bunun doğru olduğuna inanmıyorum. Hangi sürümü kullanıyorsunuz? En azından benim için çalışıyor ve MDN dokümantasyonu bunun varsayılabileceğini gösteriyor gibi görünüyor: developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/…
couchand

1
Pekala olacağım, bu uygunsuz. Her durumda, örneğim muhtemelen zayıftı, çünkü svgyine de bir öğeyi açıkça boyutlandırmanız gerekir . AFAIK, aradığınız dinamik boyuta muhtemelen güvenebileceğiniz her şey için offsetWidth.
couchand

3
Buraya React 0.14 veya üstünü kullanan herkes için artık .getDOMNode () gerekli değildir: facebook.github.io/react/blog/2015/10/07/…
Hamund

2
Bu yöntem, öğeye erişmenin en kolay (ve en çok jQuery gibi) yolu gibi görünse de, Facebook şimdi "Buna karşı öneriyoruz çünkü dize referanslarının bazı sorunları var, eski kabul ediliyor ve gelecekte büyük olasılıkla kaldırılacak. Referanslara erişmek için şu anda this.refs.textInput kullanıyorsanız, bunun yerine geri arama modelini öneririz ". Referans dizesi yerine bir geri arama işlevi kullanmalısınız. İşte Bilgisi
ahaurat

22

Couchand çözümüne alternatif olarak findDOMNode'u kullanabilirsiniz.

var Container = React.createComponent({

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

  render: function () {
    <svg />
  }
});

10
Açıklığa kavuşturmak için: React <= 0.12.x içinde component.getDOMNode (), React> = 0.13.x içinde React.findDOMNode () kullanın
pxwise

2
@pxwise Aaaaaand şimdi DOM öğesi referansları için React 0.14 ile her iki işlevi de kullanmak zorunda değilsiniz :)
Andy

5
@pxwise şimdi olduğuna inanıyorum ReactDOM.findDOMNode()?
ivarni

1
@MichaelScheper Doğru, kodumda bazı ES7 var. Güncellenen react-measurecevabımda gösteri (sanırım) saf ES6. Başlamak zor, elbette ... Geçen bir buçuk yıldır aynı çılgınlığı yaşadım :)
Andy

2
@MichaelScheper btw, burada bazı yararlı kılavuzlar bulabilirsiniz: github.com/gaearon/react-makes-you-sad
Andy

6

I kütüphanesini kullanabilirsiniz, hangi bileşenlerin oluşturulmuş boyutunu izler ve size iletir.

Örneğin:

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

Demo: https://react-sizeme-example-esbefmsitg.now.sh/

Github: https://github.com/ctrlplusb/react-sizeme

Benden çok daha zeki insanlardan ödünç aldığım optimize edilmiş kaydırma / nesne tabanlı bir algoritma kullanıyor. :)


Güzel, paylaştığın için teşekkürler. "DimensionProvidingHoC" için özel çözümüm için de bir depo oluşturmak üzereydim. Ama şimdi bunu bir deneyeceğim.
larrydahooster

Olumlu veya olumsuz herhangi bir geri bildirim için minnettar
olurum

Bunun için teşekkür ederim. <Recharts />, açık bir genişlik ve yükseklik ayarlamanızı gerektirir, bu yüzden bu çok yardımcı oldu
JP DeVries
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.