在TypeScript中使用联合类型时,我偶然发现了一个奇怪的行为.传递函数引用而不调用它时,没有类型错误:

type A = boolean;
type B = string;
type C = number; 
type BC = B | C; 

interface Props { onChange(expr: A | BC): void } 

const SomeFunction = (_props: Props) => {}
const handler = (el: A | B) => { console.log(el) } 
 
SomeFunction({onChange: handler}) // no error here 
 
SomeFunction({onChange: e => handler(e)}) // <- TS Error: Argument of type 'boolean | BC' is not assignable to parameter of type 'string | boolean'.

我能找到的唯一解释是TypeScript 3.3中的improved behavior for calling union types.

我很好奇为什么调用SomeFunction({onChange: handler})时没有错误——是因为联合类型之间存在重叠吗?

link to typescript playground

推荐答案

区别在于handler是非方法函数,而onChangeProps中被声明为方法.当您启用the --strictFunctionTypes compiler option(这是the --strict suite of compiler functionality的一部分)时,将更严格地判断非方法函数的参数.

为了类型安全,函数的参数应该判断contravariantly,这在this q/a中有描述,但简单地说,函数参数可以安全地判断narrowed而不是widened.由于Expr | PrimitiveExpr | StringOrBool宽,因此将handler视为onChange是不安全的,如添加一些功能时所示:

const SomeFunction = (_props: Props) => { _props.onChange(3) }

const handler = (el: Expr | StringOrBool) => {
    if ((typeof el !== "boolean") && (typeof el !== "object")) {
        console.log(el.toUpperCase()); // has to be a string, right?
    }
    console.log(el)
}

SomeFunction({ onChange: handler }) // runtime error: el.toUpperCase is not a function

handler函数假定非布尔非对象参数必须是字符串,但SomeFunctionnumber调用_props.onChange().哎呀!

所以在这一点上,应该很清楚,如果我们只关心类型安全,那么在both次调用SomeFunction次时应该会有一个错误.实际上,如果我们将Props重写为onChange使用非方法语法:

interface Props {
    onChange: (expr: Expr | Primitive) => void // call expression, not method
}

然后我们确实看到了两个错误:

SomeFunction({ onChange: handler }) // error! 
SomeFunction({ onChange: e => handler(e) }) // error!

但是,方法(以及所有未启用--strictFunctionTypes的函数)允许安全缩小(逆变)和不安全扩大(协方差).也就是说,bivariantly判断方法参数.为什么?事实证明,尽管这是不安全的,但它非常有用.您可以阅读this FAQ entrythis doc了解详细信息,但简而言之,它支持一些常见的编码实践(如事件处理).人们把Dog[]当作Animal[]来对待,尽管它在技术上是不安全的.如果治疗方法是安全的,那么Animal[]push()方法的存在将阻止任何人使用Dog[]作为Animal[].更多信息请参见this Q/A.

因为我们既有方法语法,也有箭头函数语法,如果我们想的话,我们可以同时使用这两种语法;只要你关心函数参数的类型安全,就不要使用方法语法.

Playground link to code

Typescript相关问答推荐

排序更改数组类型

使用新控制流的FormArray内部的Angular FormGroup

为什么TypeScript失go 了类型推断,默认为any?''

异步动态生成Playwright测试,显示未找到测试

如何设置绝对路径和相对路径的类型?

等待用户在Angular 函数中输入

不带其他属性的精确函数返回类型

自动通用回调

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

在Reaction中折叠手风琴

为什么在这种情况下,打字脚本泛型无法正确自动完成?

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

如何使函数参数数组不相交?

为什么类型脚本使用接口来声明函数?他的目的是什么.

导航链接不触发自定义挂钩

如何在Vuetify 3中导入TypeScript类型?

如何通过nx找到所有受影响的项目?

广义泛型类型不加载抽象类型上下文

当并非所有歧视值都已知时,如何缩小受歧视联盟的范围?

如果父级被删除或删除,如何自动删除子级?