繁体   English   中英

Function 返回类型基于可选参数

[英]Function return type based on optional params

我正在尝试使用自定义挂钩访问存储在 React Context 中的一组数据。

  • 如果我不传入任何参数,我想要整个数据数组
  • 如果我传入一个 ID,我希望钩子返回那个项目
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版本。 我个人倾向于这里的重载,因为条件类型更复杂并且不会带来巨大的好处。 但无论哪种方式都有效。


Playground 代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM