基于条件类型的狭义类型(Typescript)

Jur*_*can 2 typescript

我想创建需要可选参数之一的函数类型。

据我所知,是制作条件类型,但问题是在函数打字稿中无法根据此条件缩小类型

type a = { propA: number };
type b = { propB: string };

type OneOfIsRequired = <T extends a | undefined, S extends b | undefined>
  (parameterOne: S extends undefined ? a : T, parameterTwo?: T extends undefined ? b : S, x: number) => any;

const fn: OneOfIsRequired = (a, b) => {
  if (a) {
    const propA = a.propA;
  } else {
    const propB = b.propB; // Object is possibly 'undefined'.. typescript can not narrow type based on first if statement
  }
};


fn(undefined, undefined, 1); // Argument of type 'undefined' is not assignable to parameter of type 'a' OK !, one of parameter is required
fn({ propA: 1 }, undefined, 1);
fn(undefined, { propB: '1' }, 1);
Run Code Online (Sandbox Code Playgroud)

所以我希望在我的函数打字稿中的 lse 条件下可以缩小正确的类型,即类型“b”而不是“b | undefined”

知道我如何才能实现这种行为吗?我不想自己重新输入

jca*_*alz 5

我不认为条件类型对你有多大帮助。我可能会使用剩余元组的联合来描述可能的参数:

type OneOfIsRequired = (...args: [a, b | undefined, number] | [undefined, b, number]) => any;
Run Code Online (Sandbox Code Playgroud)

当您调用它时,这应该会给您相同的结果:

fn(undefined, undefined, 1); // error
fn({ propA: 1 }, undefined, 1); // okay
fn(undefined, { propB: '1' }, 1); // okay
Run Code Online (Sandbox Code Playgroud)

但它的好处是,编译器更有可能将联合缩小到其组成部分之一,而不是能够将泛型条件类型缩小到其具体值之一。


但实现仍然会抱怨,因为 TypeScript 类型保护只会缩小单个值的类型。也就是说,在 中if (a) { } else { }, 的类型可能a会在 then 和 else 子句中缩小,但是b当您检查 时, 的类型不会缩小,即使和 的a类型之间存在一些约束。 ab

The only way to have a type guard happen automatically is to have a and b part of a single value and check that single value. You could make your own object like

const fn: OneOfIsRequired = (a, b, x) => {
  const obj = { a: a, b: b } as { a: a, b: b | undefined } | { a: undefined, b: b };
  if (obj.a) {
    const propA = obj.a.propA;
  } else {
    const propB = obj.b.propB; 
  }
};
Run Code Online (Sandbox Code Playgroud)

but you already have an object kind of like this if you use the rest tuple in your implementation:

// use the arguments as a tuple
const fn: OneOfIsRequired = (...ab) => {
  if (ab[0]) {
    const propA = ab[0].propA;
  } else {
    const propB = ab[1].propB; 
  }
};
Run Code Online (Sandbox Code Playgroud)

So this works but might be more refactoring than you want to do.


If all of this is too much work for you, just admit that you are smarter than the compiler and use a type assertion to tell it so. Specifically, you can use a non-null assertion using !:

const fn: OneOfIsRequired = (a, b) => {
  if (a) {
    const propA = a.propA;
  } else {
    const propB = b!.propB; // I am smarter than the compiler 
  }
};
Run Code Online (Sandbox Code Playgroud)

That b! just means that you've told the compiler that b is not undefined, no matter what it thinks. And the error goes away. This is less type safe than the above solutions, but it is much simpler and doesn't change your emitted JavaScript.


Okay, hope that helps; good luck!