TypeScript混合已知与未知属性时调用set函数报错的解决咨询
问题:React Context中TypeScript混合已知属性与未知属性的类型错误
尝试在React Context中同时包含动态索引属性(存储各类资源)和已知方法(添加资源),代码大致如下:
interface IClient { id: string; /* 其他属性 */ } interface ICompany { id: string; /* 其他属性 */ } interface IUser { id: string; /* 其他属性 */ } interface IFoo { [key: string]: IClient | ICompany | IUser; } interface IBar { set: (arg0: IClient | ICompany | IUser) => void; } type FooBar = IFoo | IBar; const ItemContext = createContext<FooBar>(null); export function useItems() { return useContext<FooBar>(ItemContext); } export function ItemProvider({ children }) { const [items, setItems] = useState<FooBar>(null); function addItem(resource: IClient | ICompany | IUser) { setItems({ ...items, ...{ [resource.id]: resource }, }); } return ( <ItemContext.Provider value={{ ...items, set: addItem, }} >{children}</ItemContext.Provider> ); }
消费上下文调用set方法时出现错误:
export function useRouter() { const items = useItems(); function test(clientObject: IClient) { items.set(clientObject); // 此处抛出错误 } }
错误信息:
This expression is not callable.
Not all constituents of type 'IClient | ICompany | IUser | ((arg0: IClient | ICompany | IUser) => void)' are callable.
Type 'IClient' has no call signatures.ts(2349)
请问TypeScript中是否有可行方案实现已知与未知属性的混合?
解决方案
问题根源
原代码中FooBar使用了联合类型(IFoo | IBar),表示值要么是仅含动态索引的对象,要么是仅含set方法的对象。但实际上下文的value同时包含两者,TypeScript无法确定items.set到底是IBar中的函数,还是IFoo中某个索引对应的资源(如IClient),因此抛出类型错误。
正确实现:使用交叉类型
将联合类型改为交叉类型(IFoo & IBar),表示值同时拥有IFoo的动态索引属性和IBar的set方法。同时修正初始值的类型问题:
interface IClient { id: string; /* 其他属性 */ } interface ICompany { id: string; /* 其他属性 */ } interface IUser { id: string; /* 其他属性 */ } interface IFoo { [key: string]: IClient | ICompany | IUser; } interface IBar { set: (arg0: IClient | ICompany | IUser) => void; } // 改为交叉类型:同时拥有动态索引和set方法 type FooBar = IFoo & IBar; // 初始化空对象作为上下文默认值,通过类型断言处理临时类型兼容 const ItemContext = createContext<FooBar>({} as FooBar); export function useItems() { return useContext(ItemContext); } export function ItemProvider({ children }) { // useState仅存储资源部分,排除set方法 const [items, setItems] = useState<Omit<FooBar, 'set'>>({}); function addItem(resource: IClient | ICompany | IUser) { // 使用函数式更新避免闭包依赖问题 setItems(prev => ({ ...prev, [resource.id]: resource, })); } return ( <ItemContext.Provider value={{ ...items, set: addItem, }} >{children}</ItemContext.Provider> ); }
关键修改说明
- 交叉类型替代联合类型:
IFoo & IBar明确表示对象同时具备动态索引属性和set方法,TypeScript可以准确推断set是可调用的函数。 - 修正初始值类型:
null不符合交叉类型的要求,改用空对象配合类型断言处理默认值的临时类型问题,同时拆分状态为仅存储资源的部分,避免类型混淆。 - 优化状态更新逻辑:使用函数式更新(
prev => ...)避免依赖闭包中的旧items值,确保状态更新的准确性。
修改后,消费上下文时可以正常调用items.set,不会再出现类型错误。
内容的提问来源于stack exchange,提问作者allencoded
相关产品推荐
相关产品推荐

