基于React+Emotion的固定行数响应式Grid组件开发问题
实现带自适应展开按钮的Grid组件(React + Emotion + TypeScript)
核心思路
要实现所有视口下2行布局、最后一格显示展开按钮的需求,关键在于:
- 动态计算当前屏幕下的Grid列数(适配
auto-fit的可变列特性) - 根据列数控制默认显示的项数,留出最后一格放置按钮
- 用状态管理展开/折叠状态,切换Grid的行数限制
具体实现步骤
1. 状态与列数计算
用ResizeObserver监听Grid容器宽度,结合媒体查询计算当前列数(最大6列):
import { useState, useEffect, useRef } from 'react'; import styled from '@emotion/styled'; import { css } from '@emotion/react'; // 假设你的GridItem类型定义 type GridItem = { id: string; title: string; // 你的其他GridItem属性 }; const GridContainer = ({ gridItemsArr }: { gridItemsArr: GridItem[] }) => { const [isExpanded, setIsExpanded] = useState(false); const [columnCount, setColumnCount] = useState(6); // 默认桌面端列数 const gridRef = useRef<HTMLDivElement>(null); useEffect(() => { const calculateColumns = () => { if (!gridRef.current) return; // 根据媒体查询判断当前屏幕尺寸,获取GridItem宽度 const isDesktop = window.matchMedia('(min-width: 1200px)').matches; // 替换成你的desktop断点 const itemWidth = isDesktop ? 200 : 170; // 获取gap值(从CSS变量中读取) const gapValue = getComputedStyle(document.documentElement).getPropertyValue('--spacing-2xs'); const gap = gapValue ? parseInt(gapValue) : 8; // 计算列数:容器宽度 / (项宽度 + gap),取整后不超过6列 const containerWidth = gridRef.current.offsetWidth; const cols = Math.floor((containerWidth + gap) / (itemWidth + gap)); setColumnCount(Math.min(cols, 6)); }; // 初始化计算 calculateColumns(); // 监听容器尺寸变化 const observer = new ResizeObserver(calculateColumns); gridRef.current && observer.observe(gridRef.current); return () => { gridRef.current && observer.unobserve(gridRef.current); }; }, []); // 计算默认显示的项数:2行 × 列数 - 1(留最后一格给按钮) const defaultShowCount = columnCount * 2 - 1; // 判断是否需要显示按钮(总项数超过默认显示数) const needShowButton = gridItemsArr.length > defaultShowCount; // 组装要显示的内容:折叠时显示部分项+按钮,展开时显示全部 const displayContent = isExpanded ? gridItemsArr : needShowButton ? [...gridItemsArr.slice(0, defaultShowCount), { isButton: true } as unknown as GridItem] : gridItemsArr;
2. Grid组件样式调整
修改Grid组件的样式,根据展开状态切换行数限制:
const gridCollapsedStyles = css` grid-template-rows: repeat(2, 1fr); `; const gridExpandedStyles = css` grid-template-rows: repeat(auto-fit, 1fr); `; type GridProps = { isExpanded: boolean; }; const Grid = styled.div<GridProps>` display: grid; gap: var(--spacing-xs) var(--spacing-2xs); grid-template-columns: repeat(auto-fit, 170px); ${MQ("desktop")} { grid-template-columns: repeat(auto-fit, 200px); } /* 根据展开状态应用不同样式 */ ${({ isExpanded }) => isExpanded ? gridExpandedStyles : gridCollapsedStyles} `;
3. 渲染Grid项与展开按钮
在Grid中渲染内容,区分普通项和按钮:
// 展开按钮样式,和GridItem保持视觉一致 const ExpandButton = styled.button` width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border: 1px solid #e0e0e0; border-radius: 4px; background: #fff; cursor: pointer; font-size: 14px; color: #1976d2; transition: background-color 0.2s; &:hover { background-color: #f5f5f5; } `; return ( <Grid ref={gridRef} isExpanded={isExpanded}> {displayContent.map((item, index) => { // 判断是否是按钮项 if ('isButton' in item && item.isButton) { return ( <ExpandButton key="expand-btn" onClick={() => setIsExpanded(true)}> 查看更多 </ExpandButton> ); } return <GridItem key={item.id} title={item.title} />; // 替换成你的GridItem组件 })} </Grid> ); }; export default GridContainer;
关键细节说明
- 动态列数计算:用
ResizeObserver实时监听容器宽度变化,确保屏幕尺寸改变时自动调整列数和按钮位置 - 边界情况处理:当总项数不超过2行容量时,自动隐藏按钮
- 视觉一致性:展开按钮样式与GridItem保持一致,避免视觉突兀
- 性能优化:
ResizeObserver仅在容器尺寸变化时触发计算,不会频繁执行
内容的提问来源于stack exchange,提问作者ida
相关产品推荐
相关产品推荐

