5.6 KiB
5.6 KiB
🔧 StreamerConsole React Hooks 错误修复报告
问题描述
用户在访问主播控制台页面时遇到页面空白,浏览器控制台报错:
React has detected a change in the order of Hooks called by StreamerConsole
Rendered more hooks than during the previous render
根本原因分析
1. React Hooks规则违反
- 错误类型: 在条件渲染后定义了新的Hook
- 问题位置:
StreamerConsole.tsx第175-265行 - 具体表现:
loading检查后定义activeChests- 在条件渲染后又定义了
countdowns等状态Hook - 违反了"Hooks必须始终在组件顶层调用"的规则
2. 类型定义错误
- 问题: 使用了
NodeJS.Timeout类型 - 影响: 浏览器环境中该类型不存在
- 报错:
Cannot find namespace 'NodeJS'
修复方案
1. 重构Hook声明顺序
修复前(错误):
const StreamerConsole = () => {
// 基础状态Hook
const [myChests, setMyChests] = useState<Chest[]>([]);
const [loading, setLoading] = useState(true);
// ... 其他Hook
// 权限检查(条件渲染)
useEffect(() => {
if (!user) {
navigate('/login');
return;
}
loadMyChests();
}, [user, navigate]);
// 加载状态检查(条件渲染)
if (loading) {
return <Loading text="加载控制台中..." />;
}
// 计算活跃宝箱(条件渲染后)
const activeChests = myChests.filter(c => c.status === 0 || c.status === 1);
// ❌ 错误:在这里定义新的Hook!
const [countdowns, setCountdowns] = useState<{[key: number]: number}>({});
const countdownIntervalsRef = useRef<{[key: number]: NodeJS.Timeout}>({});
修复后(正确):
const StreamerConsole = () => {
// ✅ 所有Hook必须在组件顶部定义
const { user } = useAuth();
const navigate = useNavigate();
// 基础状态Hook
const [myChests, setMyChests] = useState<Chest[]>([]);
const [loading, setLoading] = useState(true);
const [showCreateForm, setShowCreateForm] = useState(false);
// ... 其他状态Hook
// ✅ 倒计时相关Hook也在顶部定义
const [countdowns, setCountdowns] = useState<{[key: number]: number}>({});
const countdownIntervalsRef = useRef<{[key: number]: number}>({});
// 权限检查(不影响Hook调用)
useEffect(() => {
if (!user) {
navigate('/login');
return;
}
loadMyChests();
}, [user, navigate]);
// 加载状态检查(不影响Hook调用)
if (loading) {
return <Loading text="加载控制台中..." />;
}
// 计算逻辑(不在Hook中)
const activeChests = myChests.filter(c => c.status === 0 || c.status === 1);
2. 修复类型定义
修复前:
// 前端环境中NodeJS.Timeout不存在
const intervalRef = useRef<NodeJS.Timeout | null>(null);
修复后:
// 浏览器环境使用number类型
const intervalRef = useRef<number | null>(null);
3. 清理未使用代码
删除了未使用的stopCountdown函数,避免TypeScript警告。
修复结果
✅ 验证测试
-
TypeScript编译: ✅ 通过
npm run build # 之前的Hook错误已消失 -
开发服务器启动: ✅ 成功
VITE v5.4.21 ready in 340 ms ➜ Local: http://localhost:3001/ -
页面渲染: ✅ 正常
- 不再出现空白页面
- 倒计时功能正常工作
- 控制台无错误
技术要点
React Hooks规则
-
只在最顶层调用Hook
- 不要在循环、条件或嵌套函数中调用
- 只在React函数中调用
-
Hook顺序必须一致
- 每次渲染时Hook的调用顺序必须相同
- 条件渲染后不能定义新Hook
-
类型定义注意事项
- 前端环境(浏览器)使用DOM API类型
NodeJS.Timeout是Node.js类型,不适用于浏览器- 浏览器中使用
number类型表示定时器ID
最佳实践
1. Hook组织结构
const Component = () => {
// 1. 基础Hook(useState, useRef等)
const [state1, setState1] = useState();
const [state2, setState2] = useState();
const ref = useRef();
// 2. 计算Hook(useMemo, useCallback)
const computed = useMemo(() => ..., []);
// 3. 副作用Hook(useEffect)
useEffect(() => {
// 副作用逻辑
}, []);
// 4. 条件返回(不影响Hook调用)
if (loading) {
return <Loading />;
}
// 5. 渲染逻辑
return <div>{computed}</div>;
};
2. 类型安全
// ✅ 正确:浏览器环境
const timerRef = useRef<number | null>(null);
// ❌ 错误:Node.js类型
const timerRef = useRef<NodeJS.Timeout | null>(null);
预防措施
-
代码审查清单
- 所有Hook在组件顶部定义
- 没有Hook在条件渲染后定义
- 使用正确的类型定义
-
IDE配置
- 启用TypeScript严格模式
- 配置ESLint规则检查Hook使用
-
测试覆盖
- 单元测试验证组件渲染
- 集成测试验证用户交互
总结
本次修复解决了React Hooks使用错误导致的页面崩溃问题。通过正确组织Hook声明顺序和修复类型定义,确保了:
- ✅ 页面正常渲染,无空白页
- ✅ 倒计时功能正常工作
- ✅ 无TypeScript类型错误
- ✅ 代码符合React最佳实践
修复文件:
frontend/src/pages/StreamerConsole.tsx- 修复Hook顺序frontend/src/components/ChestCard.tsx- 修复类型定义frontend/src/services/websocket.ts- 修复类型定义
修复时间: 2025-12-15 修复状态: ✅ 完成 测试状态: ✅ 通过