J'ai une page Web complexe utilisant des composants React et j'essaie de convertir la page d'une mise en page statique en une mise en page plus réactive et redimensionnable. Cependant, je continue à rencontrer des limitations avec React et je me demande s'il existe un modèle standard pour gérer ces problèmes. Dans mon cas particulier, j'ai un composant qui rend comme div avec display: table-cell et width: auto.
Malheureusement, je ne peux pas interroger la largeur de mon composant, car vous ne pouvez pas calculer la taille d'un élément à moins qu'il ne soit réellement placé dans le DOM (qui a le contexte complet avec lequel déduire la largeur réelle du rendu). En plus de l'utiliser pour des choses comme le positionnement relatif de la souris, j'en ai également besoin pour définir correctement les attributs de largeur sur les éléments SVG dans le composant.
De plus, lorsque la fenêtre se redimensionne, comment communiquer les changements de taille d'un composant à un autre pendant l'installation? Nous effectuons tout notre rendu SVG tiers dans shouldComponentUpdate, mais vous ne pouvez pas définir l'état ou les propriétés de vous-même ou d'autres composants enfants dans cette méthode.
Existe-t-il une manière standard de traiter ce problème en utilisant React?
la source
shouldComponentUpdate
c'est le meilleur endroit pour rendre SVG? Cela ressemble à ce que vous voulezcomponentWillReceiveProps
oucomponentWillUpdate
sinonrender
.Réponses:
La solution la plus pratique consiste à utiliser une bibliothèque pour cela comme react-measure .
Mise à jour : il existe maintenant un hook personnalisé pour la détection de redimensionnement (que je n'ai pas essayé personnellement): react-resize-aware . Étant un crochet personnalisé, il semble plus pratique à utiliser que
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> )
Pour communiquer les changements de taille entre les composants, vous pouvez passer un
onResize
rappel et stocker les valeurs qu'il reçoit quelque part (le moyen standard de partager l'état de nos jours est d'utiliser 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> ) }
Comment rouler le vôtre si vous préférez vraiment:
Créez un composant wrapper qui gère l'obtention des valeurs du DOM et l'écoute des événements de redimensionnement de fenêtre (ou la détection de redimensionnement de composant telle qu'utilisée par
react-measure
). Vous lui indiquez quels accessoires obtenir du DOM et fournissez une fonction de rendu prenant ces accessoires comme un enfant.Ce que vous rendez doit être monté avant que les accessoires DOM puissent être lus; lorsque ces accessoires ne sont pas disponibles lors du rendu initial, vous pouvez les utiliser
style={{visibility: 'hidden'}}
pour que l'utilisateur ne puisse pas les voir avant d'obtenir une mise en page calculée par 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)}/>; } }
Avec cela, répondre aux changements de largeur est très simple:
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> );
la source
isMounted()
ici: facebook.github.io/react/blog/2015/12/16/…ResizeObserver
(ou un polyfill) pour obtenir des mises à jour de taille de votre code immédiatement.Je pense que la méthode du cycle de vie que vous recherchez est
componentDidMount
. Les éléments ont déjà été placés dans le DOM et vous pouvez obtenir des informations à leur sujet à partir du composantrefs
.Par exemple:
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" /> } });
la source
offsetWidth
n'existe pas actuellement dans Firefox.svg
toute façon dimensionner explicitement un élément. AFAIK pour tout ce que vous recherchez sur la taille dynamique de vous pouvez probablement compteroffsetWidth
.Alternativement à la solution couchand, vous pouvez utiliser findDOMNode
var Container = React.createComponent({ componentDidMount: function () { var width = React.findDOMNode(this).offsetWidth; }, render: function () { <svg /> } });
la source
ReactDOM.findDOMNode()
maintenant?react-measure
démonstration est (je pense) pure ES6. C'est difficile de commencer, c'est sûr ... J'ai vécu la même folie au cours de la dernière année et demie :)Vous pouvez utiliser la bibliothèque I que j'ai écrite qui surveille la taille du rendu de vos composants et vous la transmet.
Par exemple:
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);
Démo: https://react-sizeme-example-esbefmsitg.now.sh/
Github: https://github.com/ctrlplusb/react-sizeme
Il utilise un algorithme de défilement / objet optimisé que j'ai emprunté à des personnes beaucoup plus intelligentes que moi. :)
la source