Мне интересно, какой лучший способ реализовать макетирование в приложении React.
Основы
Скажем, мы хотим иметь 4 компонента, выложенные в простой сетке. Самый простой способ - это что-то вроде этого.
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<A color="red" x={0} y={0} width={width/2} height={height/2} />
<B color="blue" x={width/2} y={0} width={width/2} height={height/2} />
<B color="green" x={0} y={height/2} width={width/2} height={height/2} />
<A color="yellow" x={width/2} y={height/2} width={width/2} height={height/2} />
</svg>
http://codepen.io/anon/pen/OWOXvV?editors=0010
Он будет работать нормально, но ввод явных значений размера является подверженным ошибкам, а не dev-friendly. Что делать, если мы могли бы использовать процентные значения (0 - 1) вместо?
Простой контейнер
const Container = ({x, y, width, height, children}) => {
return (
<g transform={`translate(${x}, ${y})`}>
{React.Children.map(children, (child) => React.cloneElement(child, { // this creates a copy
x: child.props.x * width,
y: child.props.y * height,
width: child.props.width * width,
height: child.props.height * height
}))}
</g>
);
};
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<Container width={width} height={height}>{/* one root container is given real pixel size */}
<Container width={1/2}>{/* it children recursively use 0-1 coordinates */}
<A color="red" height={1/2} />
<B color="green" y={1/2} height={1/2} />
</Container>
<Container x={1/2} width={1/2}>
<B color="blue" height={1/2} />
<A color="yellow" y={1/2} height={1/2} />
</Container>
</Container>
</svg>
http://codepen.io/anon/pen/PWEmVd?editors=0010
В этом случае мы разрешим компоненту Контейнера сопоставлять его относительные значения детей с реальными значениями пикселей. Это намного проще в использовании.
Контейнер компоновки
Другим шагом будет создание контейнера макета, т.е. HContainer, который просто кладет своих детей по горизонтали.
const HContainer = ({ x, y, width, height, children }) => {
const c = React.Children.toArray(children);
const ratio = width / c.reduce((sum, child) => (sum + child.props.width), 0);
return (
<g transform={`translate(${x}, ${y})`}>
{c.reduce((result, child) => {
const width = child.props.width * ratio;
result.children.push(React.cloneElement(child, { // this creates a copy
x: result.x,
y: 0,
width,
height
}));
result.x += width;
return result;
}, { x: 0, children: [] }).children}
</g>
);
};
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<HContainer width={width} height={height}>{/* one root container is given real pixel size */}
<Container width={1/4}>{/* it children recursively use 0-1 coordinates */}
<A color="red" height={1/2} />
<B color="green" y={1/2} height={1/2} />
</Container>
<VContainer width={3/4}>
<B color="blue" />
<A color="yellow" />
<HContainer height={1/2}>
<B color="pink" />
<A color="violet" width={3} />
<B color="#333" />
</HContainer>
</VContainer>
</HContainer>
</svg>
http://codepen.io/anon/pen/pRpwBe?editors=0010
Компоненты реагирования
Скажем, мы хотели бы удалить некоторые компоненты, когда ширина или высота ниже некоторого значения. Вы, вероятно, использовали бы условный рендеринг, подобный этому.
const MinWidth = ({ children, width, minWidth, ... others }) => {
return minWidth > width ? null : <Container width={width} {... others }>{ children }</Container>;
};
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<HContainer width={width} height={height}>{/* one root container is given real pixel size */}
<Container width={1/4}>{/* it children recursively use 0-1 coordinates */}
<A color="red" height={1/2} />
<B color="green" y={1/2} height={1/2} />
</Container>
<VContainer width={3/4}>
<B color="blue" />
<MinHeight height={1} minHeight={80}>
<A color="yellow" />
</MinHeight>
<HContainer height={1/2}>
<B color="pink" />
<A color="violet" width={3} />
<MinWidth width={1} minWidth={60}>
<B color="#333" />
</MinWidth>
</HContainer>
</VContainer>
</HContainer>
</svg>
http://codepen.io/anon/pen/dNJZGd?editors=0010
Но это оставляет пустые места, где раньше были пропущенные компоненты. Контейнеры макетов должны иметь возможность расширять отображаемые компоненты, чтобы заполнить доступное пространство.
Отзывчивый макет
И вот сложная часть. Я не вижу другого способа увидеть, будет ли компонент отображаться, но создавать и визуализировать его (и это дети). Затем, если я заложу 3 дочерних компонента в доступном пространстве и обнаруживаю, что 4-й не должен быть рендерингом, мне придется повторно отображать предыдущие 3. Это похоже на разрыв потока React.
Есть ли у кого-нибудь идеи?