Tenho uma página da web complexa que usa componentes React e estou tentando converter a página de um layout estático para um layout mais responsivo e redimensionável. No entanto, continuo encontrando limitações com o React e me pergunto se existe um padrão para lidar com esses problemas. No meu caso específico, tenho um componente que é renderizado como um div com display: table-cell e width: auto.
Infelizmente, não posso consultar a largura do meu componente, porque você não pode calcular o tamanho de um elemento a menos que ele seja realmente colocado no DOM (que tem o contexto completo para deduzir a largura real renderizada). Além de usar isso para coisas como posicionamento relativo do mouse, também preciso disso para definir corretamente os atributos de largura em elementos SVG dentro do componente.
Além disso, quando a janela é redimensionada, como comunico as alterações de tamanho de um componente para outro durante a configuração? Estamos fazendo toda a renderização SVG de terceiros em shouldComponentUpdate, mas você não pode definir o estado ou as propriedades em você mesmo ou em outros componentes filhos dentro desse método.
Existe uma maneira padrão de lidar com esse problema usando o React?
fonte
shouldComponentUpdate
é o melhor lugar para renderizar SVG? Parece que o que você quer écomponentWillReceiveProps
oucomponentWillUpdate
nãorender
.Respostas:
A solução mais prática é usar uma biblioteca para essa medida de reação semelhante .
Update : agora existe um gancho personalizado para detecção de redimensionamento (que não tentei pessoalmente): react-resize-aware . Sendo um gancho personalizado, parece mais conveniente de usar do 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 as mudanças de tamanho entre os componentes, você pode passar um
onResize
retorno de chamada e armazenar os valores recebidos em algum lugar (a forma padrão de compartilhar o estado atualmente é 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> ) }
Como fazer o seu próprio se você realmente preferir:
Crie um componente wrapper que controle a obtenção de valores do DOM e a escuta de eventos de redimensionamento de janela (ou detecção de redimensionamento de componente conforme usado por
react-measure
). Você diz a ele quais adereços obter do DOM e fornece uma função de renderização tomando esses adereços como filho.O que você renderiza precisa ser montado antes que os props DOM possam ser lidos; quando esses adereços não estão disponíveis durante a renderização inicial, você pode querer usar de
style={{visibility: 'hidden'}}
forma que o usuário não possa ver antes de obter um layout 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)}/>; } }
Com isso, responder às mudanças de largura é muito simples:
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> );
fonte
isMounted()
aqui: facebook.github.io/react/blog/2015/12/16/…ResizeObserver
(ou um polyfill) para obter atualizações de tamanho para seu código imediatamente.Acho que o método de ciclo de vida que você está procurando é
componentDidMount
. Os elementos já foram colocados no DOM e você pode obter informações sobre eles no do componenterefs
.Por exemplo:
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" /> } });
fonte
offsetWidth
pois não existe atualmente no Firefox.svg
elemento de qualquer maneira. AFAIK para qualquer coisa que você esteja procurando, o tamanho dinâmico de você provavelmente pode confiaroffsetWidth
.Como alternativa para a solução, você pode usar findDOMNode
var Container = React.createComponent({ componentDidMount: function () { var width = React.findDOMNode(this).offsetWidth; }, render: function () { <svg /> } });
fonte
ReactDOM.findDOMNode()
agora?react-measure
demonstração é (eu acho) puro ES6. É difícil começar, com certeza ... Eu passei pela mesma loucura no último ano e meio :)Você pode usar a biblioteca I que escrevi, que monitora o tamanho dos componentes renderizados e passa para você.
Por exemplo:
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
Ele usa um algoritmo de rolagem / objeto otimizado que peguei emprestado de pessoas muito mais inteligentes do que eu. :)
fonte