打字稿中可调用类型关系的问题
Posted
技术标签:
【中文标题】打字稿中可调用类型关系的问题【英文标题】:Problem with callable types relationship in typescript 【发布时间】:2020-07-09 02:29:58 【问题描述】:我对TypeScript中可调用类型之间的关系有以下问题:
type CheckExtends<T, U> = T extends U ? T : never
type NumberFunc = (op: number) => number
type AnyFunc = (op: any) => any;
type UnknownFunc = (op: unknown) => any;
type NumberAny = CheckExtends<NumberFunc, AnyFunc> // NumberFunc
type NumberUnknown = CheckExtends<NumberFunc, UnknownFunc> // never
(On the playground)
通过 cmets 显示 NumberFunc
类型是扩展 AnyFunc
类型而不是 UnknownFunc
类型。
我不明白,因为NumberFunc
类型(number
)的参数类型可以同时分配给any
和unknown
类型
【问题讨论】:
可能相关:'unknown' vs. 'any' 【参考方案1】:一旦你引入了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
【讨论】:
非常感谢@jcalz。现在对我来说很清楚了。【参考方案2】:正如您可以see in this article 一样,只有在启用strictFunctionChecks
时目标函数的参数可分配给源函数的参数时,打字稿函数才可分配。例如,如果您有以下代码:
interface A
a: number;
interface B extends A
b: number;
type AFunc = (op: A) => any;
type BFunc = (op: B) => any;
let bf: BFunc = x => x;
let af: AFunc = bf;
这将出错,因为您无法将BFunc
分配给Afunc
,因为目标参数类型A
不能分配给源参数类型B
。请注意,检查AFunc extends BFunc
是否等同于检查AFunc
是否可分配给BFunc
。您不能将NumberFunc
分配给UnknownFunc
,因为目标参数类型unknown
不能分配给源参数类型number
。这意味着NumberFunc
不会扩展UnknownFunc
。如果你在操场上禁用strictFunctionChecks
,你会看到NumberUnknown
不再是never
,它变成了NumberFunc
。
【讨论】:
以上是关于打字稿中可调用类型关系的问题的主要内容,如果未能解决你的问题,请参考以下文章