我正在开发一种打字服务,在该服务中,函数返回User | null.当throwIfNotFound参数设置为TRUE时,我希望将返回类型修改为User.这确保了在找不到用户时抛出异常,从而避免了在启用TypeScripstrictNullChecks的情况下进行多余的空判断.

可重现的例子:

interface IOptions {
    throwIfNotFound?: boolean;
}

interface User {
    id: number;
}

const userArray: User[] = [{ id: 1 }, { id: 2 }, { id: 3 }];

function findUserById(id: number, options: IOptions): User | undefined {
    const { throwIfNotFound = false } = options;

    let user: User | undefined;

    if (id !== null) {
        user = userArray.find((u) => u.id === id);
    }

    if (throwIfNotFound && !user) {
        throw new Error("User not found");
    }

    return user;
}

const user1 = findUserById(1, { throwIfNotFound: true }); //User exists so no error is thrown and user1 isn't undefined
console.log(user1.id); // 'user1' is possibly 'undefined'.ts(18048)

有没有办法使用TypeScrip来做到这一点?

谢谢

推荐答案

您希望findUserById()的输出为UserUser | undefined,具体取决于输入.但是,函数输出类型依赖于输入类型的唯一方法是将函数设置为overload(为其提供多个调用签名)或将其设置为generic.这两种方法都不能被实现中的编译器验证为类型安全(这超出了编译器的能力范围),因此从调用者的Angular 来看,每种方法都很有用.


传统上,您会使其过载,如下所示:

// call signatures
function findUserById(id: number, options: IOptions & { throwIfNotFound: true }): User;
function findUserById(id: number, options: IOptions): User | undefined;

// implementation
function findUserById(id: number, options: IOptions) {
  const { throwIfNotFound = false } = options;
  let user: User | undefined;
  if (id !== null) {
    user = userArray.find((u) => u.id === id);
  }
  if (throwIfNotFound && !user) {
    throw new Error("User not found");
  }
  return user
}

函数体仅由编译器松散地判断.如果您将判断(throwIfNotFound && !user)更改为(!throwIfNotFound && !user),编译器将无法判断出任何错误.所以要小心.

调用重载函数时,编译器通过按顺序try 每个调用签名直到找到匹配项来解析调用签名,这会给出您正在寻找的行为:

const user1 = findUserById(1, { throwIfNotFound: true });
//    ^? const user1: User

const user2 = findUserById(1, {});
//    ^? const user2: User | undefined

或者,您可以使该函数成为泛型,并根据输入类型将其返回类型设置为conditional.这种方法有时可能比重载更可取,特别是当输入输出关系太复杂而不能写成一些非通用的调用签名时.

条件类型可能如下所示:

type FindUserById<O extends IOptions> = 
  User | (O["throwIfNotFound"] extends true ? never : undefined)

其中FindUserById<O>将始终包含User,但将不包含其他任何(the never type)或undefined,具体取决于输入类型O constrainedIOptions是否具有throwIfNotFoundtrue类型(使用the indexed access type O["throwIfNotFound"]来查找该属性).

则该函数如下所示

function findUserById<O extends IOptions>(id: number, options: O): FindUserById<O> {
  const { throwIfNotFound = false } = options;

  let user: User | undefined;

  if (id !== null) {
    user = userArray.find((u) => u.id === id);
  }

  if (throwIfNotFound && !user) {
    throw new Error("User not found");
  }

  return user as FindUserById<O>
}

同样,编译器无法正确判断函数体.在这里,如果你写了return user,编译器会抱怨,因为它不能确定user是否是FindUserById<O>.因此,我们使用a type assertionas FindUserById<O>来告诉编译器,我们确信我们知道自己在做什么.同样,这是我们需要小心的事情;同样的问题在!throwIfNotFound中也会发生.

同样,从呼叫者的Angular 来看,这看起来很好:

const user1 = findUserById(1, { throwIfNotFound: true });
//    ^? const user1: User
console.log(user1.id);
const user2 = findUserById(1, {});
//    ^? const user2: User | undefined

Playground link to code

Typescript相关问答推荐

在TypScript手册中可以视为接口类型是什么意思?

我应该使用什么类型的React表单onsubmit事件?

在配置对象上映射时,类型脚本不正确地推断函数参数

泛型函数即使没有提供类型

如果字符串文字类型是泛型类型,则用反引号将该类型括起来会中断

在分配给类型的只读变量中维护const的类型

缩小并集子键后重新构造类型

CDK从可选的Lambda角色属性承担角色/复合主体中没有非空断言

声明文件中的类型继承

不带其他属性的精确函数返回类型

APP_INITIALIZER在继续到其他Provider 之前未解析promise

Typescript 类型守卫一个类泛型?

try 使Angular依赖注入工作

Angular 16将独立组件作为对话框加载,而不进行布线或预加载

TypeScrip-如何自动推断UNION变量的类型

如何通过属性名在两个泛型数组中找到匹配的对象?

TypeScrip:如何缩小具有联合类型属性的对象

推断集合访问器类型

使用RXJS获取数据的3种不同REST API

使用 TypeScript 在 SolidJS 中绘制 D3 力图