useInterval调用子函数报React Hook与void返回类型错误排查
首先你遇到的TypeError: savedCallback.current is not a function运行时错误,核心原因是第9行传给useInterval的第一个参数不是函数:你写的GetProjectScans(projectScans,isProjectScansFetchComplete)是立即执行的函数调用,执行后返回undefined,而useInterval内部会把传入的第一个参数存到savedCallback.current里,后续定时器触发时调用,传入undefined自然会报“不是函数”的错误。
另外你的代码还违反了React Hooks调用规则:useFetchProjectScansByUser是自定义Hook,只能在组件顶层或其他Hook顶层调用,不能在普通函数GetProjectScans内部调用。
针对你提出的三个具体问题,解答如下:
1. 是否需要为第4行的projectScans、isProjectScansFetchComplete显式声明变量类型?
不需要。
你已经为useFetchProjectScansByUser明确标注了返回值类型为[IProjectScan[], boolean],TypeScript会自动根据返回值类型推断解构出的两个变量的类型,额外显式声明属于冗余代码。
额外注意:你这里用let声明Hook返回的解构值是错误的,React的state是只读的,不能直接对解构出的state变量重新赋值,这也是你第6行代码的核心逻辑错误之一。
2. 为何TypeScript提示GetProjectScans内的入参变量未被使用?
两个原因:
- 你在
GetProjectScans内部没有读取这两个入参做任何逻辑判断、计算,仅在第6行尝试给这两个入参重新赋值,赋值后也没有在函数内使用这两个值,TS的未使用变量检查会直接提示入参声明后未被使用。 - 退一步说,就算你给这两个入参赋值,改动也仅在
GetProjectScans函数作用域内生效,根本修改不了组件顶层第4行解构出的state变量,完全达不到更新页面数据的效果。
3. 为何第9行的useInterval调用必须添加@ts-ignore才能通过类型校验?
因为类型不匹配。
useInterval的类型定义要求第一个参数必须是函数类型,但是你传入的是GetProjectScans(projectScans,isProjectScansFetchComplete)的执行结果——也就是undefined,和要求的函数类型完全不匹配,TS自然会抛出类型错误。加@ts-ignore只是强行跳过类型检查,并没有解决实际问题,这也是后续运行时报错的直接诱因。
正确修正方案
不要在普通函数内部调用Hook,应该把数据拉取逻辑作为方法从自定义Hook中暴露出来,再传入useInterval:
首先修改自定义Hook,暴露拉取数据的方法:
export const useFetchProjectScansByUser = (user: string|null): [IProjectScan[], boolean, () => Promise<void>] => { const [projectScans, setProjectScans] = useState<IProjectScan[]>([]); const [isProjectScansFetchComplete, setIsProjectScansFetchComplete] = useState<boolean>(false); const {enqueueSnackbar} = useSnackbar(); // 把原来useEffect里的请求逻辑抽成独立方法 const fetchScans = async () => { setIsProjectScansFetchComplete(false); try { // 此处写接口请求逻辑,拿到结果后调用setProjectScans更新数据 } catch (e) { enqueueSnackbar('拉取扫描记录失败', {variant: 'error'}); } finally { setIsProjectScansFetchComplete(true); } } // 组件首次加载、user变化时拉取一次数据 useEffect(() => { fetchScans(); }, [user]) return [projectScans, isProjectScansFetchComplete, fetchScans]; }
然后修改页面组件的调用逻辑:
const ExecutionsPage: React.FC = () => { const user: string|null = sessionStorage.getItem('user') const UPDATE_TABLE_TIMER: number = 30000; // 用const声明state,不要用let const [projectScans, isProjectScansFetchComplete, fetchProjectScans] = useFetchProjectScansByUser(user) // 直接传入函数引用,不要加括号立即执行,也不需要@ts-ignore useInterval(fetchProjectScans, UPDATE_TABLE_TIMER) // 其余页面渲染逻辑 }
内容的提问来源于stack exchange,提问作者web-dev-nerd199

