Как я могу ответить на ширину элемента DOM автоматического размера в React?
У меня есть сложная веб-страница с использованием компонентов React, и я пытаюсь преобразовать страницу из статического макета в более отзывчивый, изменяемый макет. Однако я продолжаю сталкиваться с ограничениями с React, и мне интересно, есть ли стандартный шаблон для обработки этих проблем. В моем конкретном случае у меня есть компонент, который отображается как div с display:table-cell и width:auto.
к сожалению, я не могу запросить ширина моего компонента, потому что вы не можете вычислить размер элемент, если он фактически не помещен в DOM (который имеет полный контекст, с помощью которого можно вывести фактическую отображаемую ширину). Помимо использования этого для таких вещей, как относительное позиционирование мыши, мне также нужно правильно установить атрибуты ширины на элементах SVG в компоненте.
кроме того, когда окно изменяется, как я могу сообщить изменения размера от одного компонента к другому во время установки? Мы делаем все наши сторонние SVG-рендеринги в shouldComponentUpdate, но вы не можете задайте состояние или свойства для себя или других дочерних компонентов в этом методе.
существует ли стандартный способ решения этой проблемы с помощью React?
4 ответов
наиболее практичным решением является использование реагировать-измерения.
Примечание: этот код не работает с react-measure@^2.0.0
поскольку API изменился. Посетите приведенную выше ссылку, чтобы увидеть новый API.
import Measure from 'react-measure'
const MeasuredComp = () => (
<Measure>
{({width}) => <div>My width is {width}</div>}
</Measure>
)
чтобы сообщить изменения размера между компонентами, вы можете передать onMeasure
обратный вызов и хранить значения, которые он получает где-то (стандартный способ совместного использования состояния в эти дни использовать Redux):
import Measure from 'react-measure'
import connect from 'react-redux'
import {setMyCompWidth} from './actions' // some action that stores width in somewhere in redux state
function select(state) {
return {
currentWidth: ... // get width from somewhere in the state
}
}
const MyComp = connect(select)(({dispatch, currentWidth}) => (
<Measure onMeasure={({width}) => dispatch(setMyCompWidth(width))}>
<div>MyComp width is {currentWidth}</div>
</Measure>
))
как свернуть свой собственный, если вы действительно предпочитаете:
создайте компонент-оболочку, который обрабатывает получение значений из DOM и прослушивание событий изменения размера окна (или обнаружение изменения размера компонента, используемое react-measure
). Вы говорите ему, какие реквизиты получить от DOM и предоставить функцию рендеринга, принимая эти реквизиты в качестве ребенка.
то, что вы визуализируете, должно быть смонтировано до того, как реквизит DOM можно будет прочитать; когда те реквизит недоступен во время начального рендеринга, вы можете использовать style={{visibility: 'hidden'}}
чтобы пользователь не мог видеть его, прежде чем он получит 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)}/>;
}
}
С этим, отвечая на изменения ширины очень просто:
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>
);
Я думаю, что метод жизненного цикла, который вы ищете, это componentDidMount
. Элементы уже размещены в DOM, и вы можете получить информацию о них из refs
.
например:
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" />
}
});
альтернативно решению couchand вы можете использовать findDOMNode
var Container = React.createComponent({
componentDidMount: function () {
var width = React.findDOMNode(this).offsetWidth;
},
render: function () {
<svg />
}
});
вы можете использовать библиотеку I, которую я написал, которая контролирует размер ваших компонентов и передает его вам.
например:
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);
демо:https://react-sizeme-example-esbefmsitg.now.sh/
Github:https://github.com/ctrlplusb/react-sizeme
он использует оптимизированный алгоритм на основе прокрутки / объекта, который я заимствовал у людей намного умнее, чем я. :)