ReactGridLayout报x must be a number错误排查与函数组件实现
错误根源
报错ReactGridLayout.children[0].x must be a number是多个逻辑错误叠加导致的,第一次加项时初始布局为空所以没触发校验,第二次加项时非法布局数据被回写到状态就直接触发报错:
- 初始化布局逻辑错误:构造函数里遍历
savedLayout.lg时,map回调的第一个参数i是布局项对象而非索引,x: i * 2的计算结果是NaN - 子元素属性传错:
createElement渲染子项时给data-grid传的是数组索引(纯数字),RGL读取子项布局配置时拿不到合法的x/y/w/h字段 - 缓存逻辑错误:开头直接执行
localStorage.setItem("username")未传存储值,会把username字段存为null,后续所有布局缓存的读写key都是null,会导致缓存混乱 - 依赖缺失:代码用了
_.map、_.reject但未引入lodash,本身就存在引用错误 - RGL配置缺失:使用响应式模式的RGL组件时,只传了当前layout,没传全量
layouts、断点、列数配置,状态同步逻辑不完整
修复后函数组件实现
已经把所有逻辑错误修复,完全采用函数组件+Hooks写法,保留瓦片增删、本地缓存持久化功能:
import React, { useState, useEffect, useCallback } from "react"; import { Responsive, WidthProvider } from "react-grid-layout"; import { v4 as uuid } from "uuid"; import "react-grid-layout/css/styles.css"; import "react-resizable/css/styles.css"; const ReactGridLayout = WidthProvider(Responsive); // 响应式断点配置 const BREAKPOINTS = { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }; // 各断点对应列数 const COLS = { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }; // 本地缓存key,可替换为实际登录用户名 const STORAGE_KEY = "rgl_dash_layout"; export default function Dash() { // 存储全断点布局配置 const [layouts, setLayouts] = useState({ lg: [] }); const [currentBreakpoint, setCurrentBreakpoint] = useState("lg"); // 初始化读取本地缓存 useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { try { const savedLayouts = JSON.parse(saved); setLayouts(savedLayouts); } catch (e) { console.error("布局缓存读取失败", e); } } }, []); // 布局变更时同步状态+持久化 const onLayoutChange = useCallback((currentLayout, allLayouts) => { setLayouts(allLayouts); localStorage.setItem(STORAGE_KEY, JSON.stringify(allLayouts)); }, []); // 新增瓦片 const addItem = useCallback(() => { const newId = uuid(); setLayouts(prev => { const currentLayout = prev[currentBreakpoint] || []; const newItem = { i: newId, x: (currentLayout.length * 2) % (COLS[currentBreakpoint] || 12), y: Infinity, // 自动放到最底部 w: 2, h: 2 }; return { ...prev, [currentBreakpoint]: [...currentLayout, newItem] }; }); }, [currentBreakpoint]); // 删除瓦片 const removeItem = useCallback((itemId) => { setLayouts(prev => { const currentLayout = prev[currentBreakpoint] || []; return { ...prev, [currentBreakpoint]: currentLayout.filter(item => item.i !== itemId) }; }); }, [currentBreakpoint]); // 断点切换 const onBreakpointChange = useCallback((breakpoint) => { setCurrentBreakpoint(breakpoint); }, []); return ( <div id="maincontent" className="content"> <button onClick={addItem} style={{ marginBottom: 16 }}> 新增瓦片 </button> <ReactGridLayout className="layout" resizeHandles={['se']} compactType="vertical" draggableHandle=".dragHandle" breakpoints={BREAKPOINTS} cols={COLS} layouts={layouts} onBreakpointChange={onBreakpointChange} onLayoutChange={onLayoutChange} > {(layouts[currentBreakpoint] || []).map(elem => ( <div key={elem.i} className="dragHandle" style={{ border: "1px solid #ddd", padding: 10, background: "#fff", position: "relative" }}> <span style={{ position: "absolute", right: "2px", top: 0, cursor: "pointer" }} onClick={() => removeItem(elem.i)} > x </span> 瓦片内容 {elem.i.slice(0,4)} </div> ))} </ReactGridLayout> </div> ); }
关键修复点说明
- 去掉了错误的初始化布局遍历逻辑,初始化直接读取缓存的全量断点布局
- 不再给子元素传错误的
data-grid属性,统一通过RGL的layoutsprop传配置,避免属性读取错误 - 修正了localStorage读写逻辑,固定缓存key避免存null的问题
- 去掉了未引入的lodash依赖,全部用原生数组方法实现
- 补全了响应式RGL必需的断点、列数配置,切换断点时自动同步布局状态
- 新增瓦片时y坐标设为
Infinity,会自动把新瓦片放到现有布局最底部,不会覆盖已有内容
内容的提问来源于stack exchange,提问作者Jarvis
相关产品推荐
相关产品推荐

