一旦你引入了any,你就失去了 TypeScript 中的很多类型安全保证。 any 类型故意为unsound 以允许您执行编译器认为不安全的事情。一切都可以分配给 并且 可以从any 分配;它是类型系统的逃生口。因此,NumberFunc 被视为可分配给AnyFunc 也就不足为奇了。您还会看到 AnyFunc 可分配给 NumberFunc:
type AnyNumber = CheckExtends<AnyFunc, NumberFunc> // AnyFunc
然后最好忽略any,然后使用类型安全的unknown 转到问题的另一部分:为什么NumberFunc 不能分配给UnknownFunc?
为了保持类型安全,函数的参数类型应为contravariant。这意味着函数类型之间的子类型/超类型关系与其参数的关系是相反的。例如,虽然number 是unknown 的子类型,但(x: number) => void 类型的函数是(x: unknown) => void 的超类型。因此,可分配性关系发生了翻转。起初这可能会令人困惑,但从类型安全的角度来看,它实际上是唯一有意义的事情。这是所谓的Liskov Substitution Principle 的结果;如果A 可分配给B,这意味着您可以替换任何B 类型的值与A 类型之一,它不会导致任何错误或问题。
假设我有一个类型为number 的值n。如果您要求unknown 类型的值,如果我给您n,您会很高兴。如果我给你"hello",或false,或任何值,你会很高兴。因为number 是unknown 的子类型,所以您可以在任何需要unknown 的地方使用number。
但现在假设我有一个 (x: number) => void 类型的值 f。如果你要一个(x: unknown) => void 类型的值,而我把f 交给你,你在尝试使用它时可能会很不高兴。您认为您有一个接受任何可能参数的函数,因此您希望能够像f("hello")、f(false) 或f(15) 一样调用它。但是f()只接受number参数,所以f("hello")和f(false)很可能在运行时爆炸。您要求的功能非常特定。因为在某些地方不能使用(x: number) => void,所以可以使用(x: unknown) => void,那么前者不是后者的子类型。
实际上是相反的:如果我有一个g 类型为(x: unknown) => void 的值,而你要求一个(x: number) => void 类型的值,我可以把g 交给你,你会很高兴。您只会使用g(1)、g(2) 或g(15) 等数字参数调用g(),因此您在运行时永远不会遇到问题。这意味着UnknownFunc 是NumberFunc 的子类型:
type UnknownNumber = CheckExtends<UnknownFunc, NumberFunc> // UnknownFunc
好的,希望对您有所帮助;祝你好运!
Playground link to code