Mam złożoną stronę internetową używającą komponentów React i próbuję przekonwertować stronę z układu statycznego na bardziej responsywny układ o zmiennym rozmiarze. Jednak wciąż napotykam ograniczenia w React i zastanawiam się, czy istnieje standardowy wzorzec radzenia sobie z tymi problemami. W moim konkretnym przypadku mam komponent, który renderuje się jako div z wyświetlaczem: table-cell i width: auto.
Niestety, nie mogę zapytać o szerokość mojego komponentu, ponieważ nie możesz obliczyć rozmiaru elementu, chyba że jest on faktycznie umieszczony w DOM (który ma pełny kontekst, z którego można wywnioskować rzeczywistą renderowaną szerokość). Oprócz używania tego do takich rzeczy, jak względne pozycjonowanie myszy, potrzebuję również tego do prawidłowego ustawienia atrybutów szerokości elementów SVG w komponencie.
Ponadto, gdy zmienia się rozmiar okna, w jaki sposób mogę przekazywać zmiany rozmiaru między komponentami podczas instalacji? Wykonujemy wszystkie renderowanie SVG innych firm w shouldComponentUpdate, ale nie możesz ustawić stanu ani właściwości dla siebie ani innych komponentów podrzędnych w tej metodzie.
Czy istnieje standardowy sposób radzenia sobie z tym problemem za pomocą Reacta?
źródło
shouldComponentUpdate
jest to najlepsze miejsce do renderowania SVG? Wygląda na to, że chcesz,componentWillReceiveProps
acomponentWillUpdate
jeśli nierender
.Odpowiedzi:
Najbardziej praktycznym rozwiązaniem jest skorzystanie z biblioteki do tego typu pomiaru reakcji .
Aktualizacja : istnieje teraz niestandardowy punkt zaczepienia do wykrywania zmiany rozmiaru (którego osobiście nie próbowałem): reakcja-zmiana rozmiaru-świadomość . Będąc niestandardowym haczykiem, wygląda na wygodniejszy w użyciu niż
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> )
Aby przekazać zmiany rozmiaru między komponentami, możesz przekazać
onResize
wywołanie zwrotne i gdzieś zapisać otrzymane wartości (obecnie standardowym sposobem udostępniania stanu jest użycie 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> ) }
Jak rzucić własne, jeśli naprawdę wolisz:
Utwórz komponent opakowujący, który obsługuje pobieranie wartości z DOM i nasłuchiwanie zdarzeń zmiany rozmiaru okna (lub wykrywania zmiany rozmiaru komponentu, jak jest używane przez
react-measure
). Mówisz mu, które rekwizyty pobrać z DOM-a i udostępniasz funkcję renderującą, która przyjmuje te właściwości jako dziecko.To, co renderujesz, musi zostać zamontowane, zanim będzie można odczytać właściwości DOM; kiedy te właściwości nie są dostępne podczas początkowego renderowania, możesz zechcieć ich użyć,
style={{visibility: 'hidden'}}
aby użytkownik nie mógł ich zobaczyć, zanim otrzyma układ obliczony w 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)}/>; } }
Dzięki temu reagowanie na zmiany szerokości jest bardzo proste:
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> );
źródło
isMounted()
tutaj: facebook.github.io/react/blog/2015/12/16/…ResizeObserver
(lub polyfill) do natychmiastowego pobierania aktualizacji rozmiaru do twojego kodu.Myślę, że metoda cyklu życia, której szukasz, to
componentDidMount
. Elementy zostały już umieszczone w DOM i możesz uzyskać informacje o nich z komponenturefs
.Na przykład:
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" /> } });
źródło
offsetWidth
obecnie nie istnieje w przeglądarce Firefox.svg
elementu. AFAIK na wszystko, czego szukasz, biorąc pod uwagę dynamiczny rozmiar, na którym prawdopodobnie możesz polegaćoffsetWidth
.Alternatywnie do rozwiązania couchand możesz użyć findDOMNode
var Container = React.createComponent({ componentDidMount: function () { var width = React.findDOMNode(this).offsetWidth; }, render: function () { <svg /> } });
źródło
ReactDOM.findDOMNode()
teraz?react-measure
demonstracją jest (myślę) czysty ES6. Na pewno ciężko jest zacząć ... Przeszedłem przez to samo szaleństwo przez ostatnie półtora roku :)Mógłbyś skorzystać z biblioteki I, którą napisałem, która monitoruje rozmiar renderowanych komponentów i przekazuje je do Ciebie.
Na przykład:
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
Używa zoptymalizowanego algorytmu opartego na przewijaniu / obiektach, który pożyczyłem od ludzi znacznie sprytniejszych niż ja. :)
źródło