基于Material UI封装方法实现Box布局:3个一排且第4个叠于首个下方
问题描述
本人是Material UI、TypeScript和React新手,若表述有误或术语使用不当请予以指正。我需要在页面上布局4个Box,要求其中3个横向排成一行,第4个堆叠在第一个Box的下方。
目前我使用Grid容器作为父组件来包裹这些Box,代码如下:
<Grid container={true} spacing={1} justifyContent="center" alignItems='flex-start'> <Box sx={{m: 1}} style={{width: '500px', height: '600px'}}> <SomeData data={defaultDeploymentFrequencyData}/> </Box> <Box sx={{m: 1}} style={{width: '500px', height: '600px'}}> <SomeData data={defaultDeploymentFrequencyData}/> </Box> <Box sx={{m: 1}} style={{width: '500px', height: '600px'}}> <SomeData data={defaultDeploymentFrequencyData}/> </Box> <Box sx={{m: 1}} style={{width: '500px', height: '600px'}}> <SomeData data={defaultDeploymentFrequencyData}/> </Box> </Grid>
我不想重复编写Box代码,希望通过调用一个方法来实现正确的布局。我尝试编写了如下BoxLayout组件:
export const BoxLayout = ():JSX.Element => { return ( <Box> sx={{ display: 'grid', gap: 1, gridTemplateColumns: 'repeat(2, 1fr)', }} </Box> ) }
并设想了如下调用方式:
<Grid container={true} spacing={1} justifyContent="center" alignItems='flex-start'> <BoxLayout> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> </BoxLayout> </Grid>
请问该如何正确实现这种布局调用?
解决方案
1. 修正现有BoxLayout的问题
你的BoxLayout存在两个核心错误:
sx属性被错误地写在Box标签内部,应该作为props传递给Box- 没有接收并渲染子组件(
children),导致传入的SomeData无法显示
2. 实现符合需求的布局组件
下面提供两种适配需求的实现方式,都能实现3个Box横向一行,第4个堆叠在第一个下方的效果,同时复用Box的样式代码:
方式一:基于Material UI Grid系统实现
贴合你最初的思路,利用MUI Grid的栅格逻辑拆分布局:
import { Box, Grid } from '@mui/material'; import { ReactNode } from 'react'; interface BoxLayoutProps { children: ReactNode[]; } export const BoxLayout = ({ children }: BoxLayoutProps): JSX.Element => { return ( <Grid container spacing={1} justifyContent="center" alignItems="flex-start"> {/* 第一列:放置第1、4个Box */} <Grid item> {children.slice(0, 1).map((child, idx) => ( <Box key={idx} sx={{ m: 1, width: '500px', height: '600px' }}> {child} </Box> ))} {children.slice(3, 4).map((child, idx) => ( <Box key={idx + 1} sx={{ m: 1, width: '500px', height: '600px' }}> {child} </Box> ))} </Grid> {/* 第二、三列:分别放置第2、3个Box */} {children.slice(1, 3).map((child, idx) => ( <Grid item key={idx + 2}> <Box sx={{ m: 1, width: '500px', height: '600px' }}> {child} </Box> </Grid> ))} </Grid> ); };
调用方式:
<BoxLayout> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> <SomeData data={defaultDeploymentFrequencyData}/> </BoxLayout>
方式二:基于CSS Grid实现
用纯CSS Grid的区域定义来控制布局,代码更简洁:
import { Box } from '@mui/material'; import { ReactNode } from 'react'; interface BoxLayoutProps { children: ReactNode[]; } export const BoxLayout = ({ children }: BoxLayoutProps): JSX.Element => { return ( <Box sx={{ display: 'grid', gap: 1, // 定义网格区域,明确每个Box的位置 gridTemplateAreas: ` "box1 box2 box3" "box4 box2 box3" `, justifyContent: 'center', padding: 1, }} > {children.map((child, idx) => ( <Box key={idx} sx={{ m: 1, width: '500px', height: '600px', gridArea: idx === 0 ? 'box1' : idx === 1 ? 'box2' : idx === 2 ? 'box3' : 'box4', }} > {child} </Box> ))} </Box> ); };
调用方式和上述一致,直接传入4个SomeData组件即可。
3. 核心说明
- 两种方式都实现了Box样式的复用,避免重复代码
- 通过接收
children动态渲染内容,符合React组件复用逻辑 - 后续调整Box样式时,只需修改
BoxLayout中的sx属性即可
内容的提问来源于stack exchange,提问作者Max Bojorquez
相关产品推荐
相关产品推荐

