React自定义Hook返回数组时出现"This expression is not callable"错误的技术问询
解决React自定义Hook数组解构的TypeScript类型报错问题
这个问题的核心是TypeScript对数组返回值的类型推断逻辑:默认情况下,它会把你返回的[body, setProp, setBody]推断成一个联合类型数组(每个元素的类型是jsonType | 函数 | 函数的混合),而不是你期望的固定顺序的元组。这就导致TypeScript无法确定你解构出来的第二个、第三个元素到底是可调用的函数还是普通对象,所以会抛出"This expression is not callable"的错误。
两种可行的解决方案
方案1:显式指定Hook的返回类型为元组
通过定义明确的元组类型,直接告诉TypeScript返回数组中每个位置的具体类型:
import { useState, Dispatch, SetStateAction } from 'react'; // 基础JSON类型定义 type JsonType = { [property: string]: any }; // 定义Hook的返回元组类型:[当前状态, 设置单个属性的函数, 整体设置状态的函数] type UseJSONStateReturn<T extends JsonType> = [ T, (property: keyof T, value: T[keyof T]) => void, Dispatch<SetStateAction<T>> ]; const useJSONState = <T extends JsonType>(json: T): UseJSONStateReturn<T> => { const [body, setBody] = useState<T>(json); // 优化setProp:使用函数式更新,避免闭包导致的旧值问题 const setProp = (property: keyof T, value: T[keyof T]) => { setBody(prevBody => ({ ...prevBody, [property]: value })); }; return [body, setProp, setBody]; }; export default useJSONState;
这里还做了两个实用优化:
- 用泛型
T代替固定的JsonType,让Hook支持特定结构的JSON,类型检查更严格 - 使用函数式更新
setBody(prevBody => ...),避免依赖外部body变量导致的状态更新异常
方案2:使用as const断言简化类型推断
如果你不想额外定义返回类型,可以用as const断言让TypeScript把返回数组推断为只读元组,固定每个位置的类型:
import { useState } from 'react'; type JsonType = { [property: string]: any }; const useJSONState = <T extends JsonType>(json: T) => { const [body, setBody] = useState<T>(json); const setProp = (property: keyof T, value: T[keyof T]) => { setBody(prevBody => ({ ...prevBody, [property]: value })); }; // 用as const断言强制TypeScript推断为固定顺序的元组 return [body, setProp, setBody] as const; }; export default useJSONState;
使用效果
现在你就可以像useState一样自由解构,而且TypeScript会正确识别每个函数的类型:
// 示例调用 const [userInfo, setUserProp, setUserInfo] = useJSONState({ name: "Alice", age: 30 }); // 类型检查生效:只能传入存在的属性,值的类型也会自动匹配 setUserProp("age", 31); // ✅ 正确 setUserProp("name", "Bob"); // ✅ 正确 setUserProp("invalidKey", "test"); // ❌ TypeScript报错:invalidKey不是userInfo的属性
内容的提问来源于stack exchange,提问作者Soerman
相关产品推荐
相关产品推荐

