React Hook类型错误:ReturnType<typeof setTimeout>与setTimeout返回类型不匹配
React Hook类型校验错误:setTimeout返回类型不兼容
问题场景
创建自定义React Hook useTimeoutEffect 时,遇到TypeScript类型校验失败,错误提示Type 'number' is not assignable to type 'Timeout',出现在给useRef赋值setTimeout返回值的行。
Hook代码
import { useEffect, useRef } from "react"; export function useTimeoutEffect<TArgs extends any[]>( callback: (...args: TArgs) => void, ms?: number, ...args: TArgs ): void { const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); useEffect(() => { if (timeoutRef.current !== undefined) { clearTimeout(timeoutRef.current); } // 类型错误行 timeoutRef.current = setTimeout(callback, ms, args); return () => { clearTimeout(timeoutRef.current); timeoutRef.current = undefined; }; }, [ms, args]); }
tsconfig.json配置
{ "compilerOptions": { "target": "esnext", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": false, "skipLibCheck": true, "declarationMap": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "declaration": true, "outDir": "./dist", "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "react-jsx", "noEmit": true }, "include": [ "src" ], "exclude": [ "**/*.test.ts", "**/*.test.tsx", "src/test-support/*" ] }
错误信息
$ C:\p\spring-docker\node_modules\.bin\tsc src/useTimeoutEffect/useTimeoutEffect.ts:19:5 - error TS2322: Type 'number' is not assignable to type 'Timeout'. 19 timeoutRef.current = setTimeout(callback, ms, args); ~~~~~~~~~~~~~~~~~~ Found 1 error in src/useTimeoutEffect/useTimeoutEffect.ts:19
已尝试的操作
- 使用
ReturnType<typeof setTimeout>定义useRef的类型 - 未在tsconfig中显式添加
"types": ["node"] - 尝试添加
"types": []清空类型依赖,问题依旧 - 目前临时通过
as any绕过错误,但需要合理解决方案
问题原因
类型冲突来自环境差异:
- 浏览器环境中,
setTimeout返回数字类型的定时器ID - Node.js环境中(项目通过jest引入了
@types/node),setTimeout返回**Timeout对象**
TypeScript同时加载了DOM和Node的类型定义,导致ReturnType<typeof setTimeout>的类型歧义,最终校验失败。
解决方案
方案1:明确使用浏览器环境的定时器类型
直接将useRef的类型定义为浏览器环境的number | undefined,同时修复参数传递和依赖数组的问题:
import { useEffect, useRef } from "react"; export function useTimeoutEffect<TArgs extends any[]>( callback: (...args: TArgs) => void, ms?: number, ...args: TArgs ): void { // 浏览器环境下定时器ID为number类型 const timeoutRef = useRef<number | undefined>(); useEffect(() => { if (timeoutRef.current !== undefined) { clearTimeout(timeoutRef.current); } // 修复参数传递:用...args展开,确保回调接收正确的参数 timeoutRef.current = setTimeout(callback, ms, ...args); return () => { clearTimeout(timeoutRef.current); timeoutRef.current = undefined; }; }, [ms, ...args]); // 依赖数组展开args,避免因数组引用变化导致不必要的effect执行 }
方案2:显式指定环境类型(兼容多环境)
如果需要同时兼容浏览器和Node.js环境,可以通过类型断言明确指定,并保留ReturnType<typeof setTimeout>的定义:
// 保持原useRef类型定义 const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); // 在赋值时添加类型断言 timeoutRef.current = setTimeout(callback, ms, ...args) as ReturnType<typeof setTimeout>;
方案3:限制tsconfig的类型依赖
在tsconfig中明确只加载DOM类型,避免Node类型干扰:
{ "compilerOptions": { // ...其他配置 "types": ["dom"] } }
注:如果需要在jest测试中使用Node类型,可以创建单独的tsconfig.test.json,添加"types": ["dom", "node"]。
内容的提问来源于stack exchange,提问作者Archimedes Trajano
相关产品推荐
相关产品推荐

