[英]Function return type based on optional params
我正在尝试使用自定义挂钩访问存储在 React Context 中的一组数据。
type Data = { id: string };
const useMyData = (id?: string) => {
const data: Data[] = useContext(MyDataProvider);
if (id) return data.find(item => item.id === id); // type: Data
return data; // type: Data[]
}
我试过 function 重载,但我得到This overload signature is not compatible with its implementation signature.
function useMyData(): Data[]
function useMyData(id?: string): Data { /* ... */ }
我试过 Generics 无济于事: Type 'Data' is not assignable to type 'T extends undefined? Data[]: Data
Type 'Data' is not assignable to type 'T extends undefined? Data[]: Data
const useMyData = <T extends string | undefined>(
id?: T,
): T extends undefined ? Data[] : Data => { /* ... */ };
我试过键入钩子,但我得到了你可以想象的“不可分配给”错误的每一种组合。 Data | Data[] is not assignable to Data[]
Data | Data[] is not assignable to Data[]
, unknown is not assignable to Data
,等等。
type TUseMyData = (() => Data[]) | ((id: string) => Data);
const useMyData: TUseMyData = (id?: string) => { /* ... */ };
我将使用这个实现开始:
const theData: Data[] = [{ id: "a" }, { id: "b" }, { id: "c" }];
const useMyDataUnion = (id?: string) => {
if (typeof id === "string")
return theData.find(item => item.id === id); // type: Data | undefined
return theData; // type: Data[]
}
请注意,返回类型是Data[]
或Data | undefined
Data | undefined
,因为Array.prototype.find()
不能保证找到任何东西。 function 具有类型签名
// const useMyDataUnion: (id?: string | undefined) => Data | Data[] | undefined
这是真的,但不足以满足您的需求。
首先,我们将使用重载来做到这一点:
function useMyDataOverload(): Data[]; // call signature 1
function useMyDataOverload(id: string): Data | undefined; // call signature 2
// implementation:
function useMyDataOverload(id?: string) {
return useMyDataUnion(id);
}
请注意,重载将调用签名与 function 的实现分开,并且 function 的实现不会添加到调用签名列表中。 在您的示例中,您将实现视为另一个调用签名,并且编译器不满意。
对于useMyDataOverload()
,有两种调用方式。 您可以不带参数调用它并获取Data[]
,或者使用string
参数调用它并获取Data | undefined
Data | undefined
。 实现必须接受两种可能的调用方式......单个可选string
参数符合该标准。 它还必须返回与调用签名的返回类型联合兼容的类型。 它也符合这个标准。 请注意,返回类型规则对于类型安全来说是必要的,但还不够。 您可以轻松地将实现更改为:
function useMyDataBadOverload(): Data[];
function useMyDataBadOverload(id: string): Data | undefined;
function useMyDataBadOverload(id?: string) {
return Math.random() < 0.5 ? theData.find(i => Math.random() < 0.5) : theData;
}
这肯定会返回一个Data[] | Data | undefined
Data[] | Data | undefined
Data[] | Data | undefined
但不保证返回正确的类型。 所以要小心实现过载的 function。
让我们看看它是否有效:
console.log(useMyDataOverload().map(x => x.id)); // ["a", "b", "c"]
console.log(useMyDataOverload("a")?.id); // "a"
console.log(useMyDataOverload("z")?.id); // undefined
看起来一切正常,编译器接受它。 我认为重载可能是正确的解决方案。 一个缺点是您一次只能调用一个调用签名; 如果编译器无法判断您调用的是哪一个,则会出现错误:
const params = Math.random() < 0.5 ? [] as const : ["a"] as const;
useMyDataOverload(...params); // error, no acceptable overload
您还可以尝试使用带有条件类型的通用function 。 我的实现如下所示:
const useMyDataConditional = <T extends [id: string] | []>(
...params: T
) => useMyDataUnion(params[0]) as T extends [] ? Data[] : (Data | undefined);
// need type assertion
这是使用T
作为参数列表的类型,并且是单个string
参数或空参数列表。 返回类型是有条件的,类似于您所做的。
但正如您所注意到的,编译器无法验证返回值是否可分配给通用条件返回类型。 重载也是如此,但是对于重载,编译器故意对类型检查“松懈”,并让事情通过它无法确定。 在这里,它是“严格的”,几乎没有通过。 如果我只是注释function,我会得到一个错误:
const useMyDataBadConditional = <T extends [id: string] | []>(
...params: T): T extends [] ? Data[] : (Data | undefined) =>
useMyDataUnion(params[0]); // error
有一个未解决的问题microsoft/TypeScript#33912 ,要求编译器以某种方式检查 function 实现以验证它是否可分配给通用条件类型。 不过现在,我推荐的解决方案是type assertion 。 不要问编译器返回类型是不是T extends []? Data[]: (Data | undefined)
T extends []? Data[]: (Data | undefined)
; 相反,你用as
告诉它。
让我们确保它有效:
console.log(useMyDataConditional().map(x => x.id)); // ["a", "b", "c"]
console.log(useMyDataConditional("a")?.id); // "a"
console.log(useMyDataConditional("z")?.id); // undefined
useMyDataConditional(...params); // Data | Data[] | undefined
所有这些都按预期工作,甚至支持...params
版本。 我个人倾向于这里的重载,因为条件类型更复杂并且不会带来巨大的好处。 但无论哪种方式都有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.