网站搜索

typescript - 为什么 TS 不会将我的类型参数完全求值,我该如何解决这个问题?


我有一个泛型类型,它有点复杂,包括一个联合类型,其中只有在类型参数 T 扩展了 undefined 时才允许某个元素。

type PropValidator<T> = {
  [k in keyof T & string]:
    // simple form - just a function, implies required
    | Function
    
    // validate a required property
    | {
        required: true;
        validate: Function;
      }

    // validate a property that is allowed to be undefined
    | (undefined extends T[k] // <-- this is the key part
        ? {
            required: false;
            validate: Function;
          }
        : never);
};

我理解这意味着 { required: false, validate:() => {} } 不是有效的,除非 T 上的属性允许 undefined 作为值。所以在你不知道 undefined extends T[K] 是否成立的泛型上下文中无法使用它是有道理的。

但是在 T 已经被约束的上下文中呢?例如,SomeType:

// something you might validate
type SomeType<T = string> = {
  prop?: T
}

// a type that only validates SomeType objects
type SomeTypeValidator<T> = PropValidator<SomeType<T>>

我们知道 undefined 总是会 extend SomeType<T>['prop'],因为 prop 在 SomeType 定义中是可选的,因此 SomeTypeValidator<T> 应该总是允许 { required: false, validate:() => {} }

但是当我尝试在通用上下文中给它设置一个值时,它会拒绝这一操作:

function constrainedGeneric<T extends string>(prop: SomeTypeValidator<T>['prop']) {
  prop = {
    required: false, // Type '{ required: false; validate: () => void; }' is not assignable to type 'Function | { required: true; validate: Function; } | (undefined extends T | undefined ? { required: false; validate: Function; } : never)'.
                     // Object literal may only specify known properties, and 'required' does not exist in type 'never'.'SomeTypeValidator<T>'
    validate: () => {}
  }
}

我哪里出了错,我该如何解决这个问题?我如何修改我的定义,使其意思不变,但允许我在 constrainedGeneric 函数中设置那个字面量 { required: false } 值?

便捷的 Playground 链接

答案#1

(我不得不稍微重构一下,以便一开始对我来说更清楚。)

我几乎可以通过在最后的赋值中对对象进行类型转换(见代码)使其完全工作,并且无需特定的类型参数约束:现在编译按预期成功/失败,并且该 propValidator 的单个成员看起来也没问题。

唯一的问题是,这可能是 TS(v5.2.2)中条件类型的一个限制,即在最后一种情况下,propValidator 的类型没有被“解析”:不过,无论如何,返回它似乎工作正常(见 test 函数的返回类型)...

type SimplePropValidator = Function;
type RequiredPropValidator = {
  required: true;
  validate: Function;
};
type OptionalPropValidator = {
  required: false;
  validate: Function;
};

type ObjectValidator<T> = {
  [K in keyof T]:
  | SimplePropValidator
  | RequiredPropValidator
  | (undefined extends T[K] ? OptionalPropValidator : never);
};

type SomeType<P> = { prop?: P; }

type SomeTypeValidator<P> = ObjectValidator<SomeType<P>>

function test<P>(): OptionalPropValidator {
  let propValidator: SomeTypeValidator<P>["prop"];

  propValidator = () => { };
  propValidator;  // Function

  propValidator = {
    required: true,
    validate: () => { }
  };
  propValidator;  // RequiredPropValidator

  // fails compilation if `prop` is NOT optional, as expected:
  propValidator = {
    required: false,
    validate: () => { }
  } as OptionalPropValidator;
  propValidator;  // undefined extends P | undefined ? OptionalPropValidator : never
  propValidator.required; // false
  propValidator.validate; // Function

  return propValidator;
}

游乐场链接

附录:

假设这与条件类型有关,我尝试了这个变体,确实它有效:在最后一次赋值之后,propValidator 的类型会被解析(不是立即解析,而是在下一行解析,参见 playground 中的代码),并且不需要任何类型转换...

附言再附言 但现在这种行为发生在每一个作业上,而不仅仅是最后一次:类型只有在第一次使用后才会被解析(不过我没有费力去修正游乐场里的注释)。所以我必须收回之前认为这比之前的解决方案更令人满意的说法。

正如我在评论中提到的,我认为这是 TypeScript 处理条件类型时的一个(很可能的)限制/错误,可能值得在 GitHub 上报告为一个问题。

type ObjectValidator<T> = {  // refactored definition
  [K in keyof T]: (
    undefined extends T[K] ?
    (SimplePropValidator | RequiredPropValidator | OptionalPropValidator) :
    (SimplePropValidator | RequiredPropValidator)
  );
};

游乐场链接(2)