Tengo una página web compleja que usa componentes React y estoy tratando de convertir la página de un diseño estático a un diseño más sensible y redimensionable. Sin embargo, sigo encontrando limitaciones con React y me pregunto si existe un patrón estándar para manejar estos problemas. En mi caso específico, tengo un componente que se representa como un div con display: table-cell y width: auto.
Desafortunadamente, no puedo consultar el ancho de mi componente, porque no se puede calcular el tamaño de un elemento a menos que esté realmente colocado en el DOM (que tiene el contexto completo con el cual deducir el ancho real renderizado). Además de usar esto para cosas como el posicionamiento relativo del mouse, también lo necesito para establecer correctamente los atributos de ancho en los elementos SVG dentro del componente.
Además, cuando la ventana cambia de tamaño, ¿cómo comunico los cambios de tamaño de un componente a otro durante la instalación? Estamos haciendo todo nuestro renderizado SVG de terceros en shouldComponentUpdate, pero no puede establecer el estado o las propiedades en usted o en otros componentes secundarios dentro de ese método.
¿Existe una forma estándar de lidiar con este problema usando React?
fuente
shouldComponentUpdate
es el mejor lugar para renderizar SVG? Parece que lo que quieres escomponentWillReceiveProps
ocomponentWillUpdate
si norender
.Respuestas:
La solución más práctica es usar una biblioteca para esto como react-medir .
Actualización : ahora hay un gancho personalizado para la detección de cambio de tamaño (que no he probado personalmente): react-resize-awareness . Al ser un gancho personalizado, parece más cómodo de usar 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> )
Para comunicar cambios de tamaño entre componentes, puede pasar una
onResize
devolución de llamada y almacenar los valores que recibe en algún lugar (la forma estándar de compartir el estado en estos días es usar 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> ) }
Cómo enrollar el tuyo si realmente prefieres:
Cree un componente contenedor que maneje la obtención de valores del DOM y escuche los eventos de cambio de tamaño de la ventana (o la detección de cambio de tamaño del componente como lo usa
react-measure
). Le dice qué accesorios obtener del DOM y proporciona una función de renderizado que toma esos accesorios como un niño.Lo que renderiza debe montarse antes de que se puedan leer los accesorios DOM; cuando esos accesorios no están disponibles durante el procesamiento inicial, es posible que desee usarlos
style={{visibility: 'hidden'}}
para que el usuario no pueda verlos antes de que obtenga un diseño calculado por 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)}/>; } }
Con esto, responder a los cambios de ancho es muy 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> );
fuente
isMounted()
aquí: facebook.github.io/react/blog/2015/12/16/…ResizeObserver
(o un polyfill) para obtener actualizaciones de tamaño de su código de inmediato.Creo que el método de ciclo de vida que estás buscando es
componentDidMount
. Los elementos ya se han colocado en el DOM y puede obtener información sobre ellos en el archivorefs
.Por ejemplo:
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" /> } });
fuente
offsetWidth
no existe actualmente en Firefox.svg
todos modos debe dimensionar explícitamente un elemento. AFAIK para cualquier cosa que esté buscando, el tamaño dinámico del que probablemente pueda confiaroffsetWidth
.Alternativamente a la solución Couchand, puede usar findDOMNode
var Container = React.createComponent({ componentDidMount: function () { var width = React.findDOMNode(this).offsetWidth; }, render: function () { <svg /> } });
fuente
ReactDOM.findDOMNode()
ahora?react-measure
demostración es (creo) ES6 puro. Es difícil comenzar, seguro ... Pasé por la misma locura durante el último año y medio :)Puede usar la biblioteca I que escribí, que monitorea el tamaño de representación de sus componentes y se lo pasa.
Por ejemplo:
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);
Demostración: https://react-sizeme-example-esbefmsitg.now.sh/
Github: https://github.com/ctrlplusb/react-sizeme
Utiliza un algoritmo optimizado basado en objetos / desplazamiento que tomé prestado de personas mucho más inteligentes que yo. :)
fuente