typescript 基于第二个参数使用类型 predicate 缩小类型范围

64jmpszr  于 2023-03-09  发布在  TypeScript
关注(0)|答案(2)|浏览(115)

我有一个验证函数,它接收要验证的值,还接收用于验证的配置对象。

const config = {type: 'foo'}
const value = {name: 'John', age: 22}

const isValid = (value: Value, config: Config) {
  // some validation over here
}

这个值是两种类型的并集。我希望验证函数不仅能根据配置验证这个值,而且能将这个值的类型缩小到两种类型的并集之一。

type Value = Foo | Bar

根据配置,我知道应该将范围缩小到这两者中的哪一个。逻辑应该是这样的:

  • 如果config.typefoo,并且验证函数返回true,则其类型应为Foo
  • 如果config.typebar,并且验证函数返回true,则其类型应为Bar
  • 如果验证返回false,则类型应保持为Value

示例:

type Config = {type: 'foo' | 'bar'}
type Foo = {name: string, age: number, something?: string}
type Bar = {name: string, age: number, somethingElse?: string}
type Value = Foo | Bar
const value: Value = {name: 'John', age: 22}

const config: Config = {type: 'foo'}
if (isValid(value, config)) {
 // value should be narrowed down to Foo
}

const config2: Config = {type: 'bar'}
if (isValid(value, config2)) {
 // value should be narrowed down to Bar
}

问题是,我应该如何修改isValid函数,以便它根据配置和返回值缩小类型范围?
注意,配置在代码中是常量(尽管有多个不同的配置)。

fxnxkyjh

fxnxkyjh1#

对于所提出的问题,我倾向于在K中创建isValid()generic,即config参数的type属性的literal type,并在适当的Map接口中使用类型 predicate 以looking upK为单位缩小value

interface ValMap {
    foo: Foo,
    bar: Bar
}
declare const isValid: <K extends keyof ValMap>(
    value: Value,
    config: { type: K }
) => value is ValMap[K]

那么我们可以这样使用它:

declare const value: Value;

const config = { type: 'foo' } satisfies Config;
if (isValid(value, config)) {
    value; // Foo
    value.something
}

const config2 = { type: 'bar' } satisfies Config;
if (isValid(value, config2)) {
    value; // Bar
    value.somethingElse;
}

注意,我们不能只写const config = {type: "foo"},否则编译器会推断它具有{type: string}类型,该类型太宽而没有用,甚至不能赋值给Config,有不同的方法可以让编译器推断出更合适的类型;你可以在初始化器上使用constAssert,或者你可以做我上面所做的,并使用satisfies操作符来提供你试图获得Config兼容类型的上下文(而不是实际上一直扩展到Config,这也不会有用)。
无论如何,它看起来不错...当isValid()返回true时,它在正确的位置将value缩小为FooBar
Playground代码链接

6rvt4ljy

6rvt4ljy2#

你想实现这样的目标吗?

type Foo = { name: string; age: number; something?: string };
type Bar = { name: string; age: number; somethingElse?: string };
type Value = Foo | Bar;
const value: Value = { name: "John", age: 22 };

type Config<T extends Value> = { type: "foo" | "bar" };

const isValid = <T extends Value>(
  value: Value,
  config: Config<T>
): value is T => {
  return false;
};

const config: Config<Foo> = { type: "foo" as const };
if (isValid(value, config)) {
  // value should be narrowed down to Foo
  console.log(value);
}

const config2: Config<Bar> = { type: "bar" };
if (isValid(value, config2)) {
  // value should be narrowed down to Bar
  console.log(value);
}

相关问题