问题
前段时间在 stackoverflow上看到一个提问,提问者在使用window.fetch时遇到了一个问题:
Object literal may only specify known properties, and ''attr'' does not exist in type 'Headers'
最后排查出来的原因可能是attr属性可为undefined。
Type '{ 'attr': string | undefined; }' is not assignable to type 'HeadersInit | undefined'.
Type '{ 'attr': string | undefined; }' is not assignable to type 'undefined'. TS2322
window.fetch('url', {
headers: {
↑
attr: attr
}
})
type HeadersInit = Headers | string[][] | Record<string, string>;
我追到lib.dom.d.ts里面去看Headers的类型是HeadersInit,在本地试了一下,没有出现上述的问题,对象的属性值赋值为undefined编译通过。我想这可能是tsconfig.json里面的lib选项配置有问题,导致用的不是lib.dom.d.ts。虽然本地没有复现,但是问题出现在别人那里,也可以尝试着去解决。
解决方案
对于输入的类型和定义的类型不兼容的情况,TypeScript能给出的解决方案也是不一而足:
使用Headers API
如果内置lib提示不能使用对象字面量对headers进行赋值,可以使用Headers构造函数,通过方法调用来设置头部键值对。
const headers = new Headers()
headers.set('attr', attr)
fetch('url', { headers })
直接跳过编译检查
使用注释@ts-ignore或@ts-nocheck跳过编译检查,其中@ts-ignore需要添加在需要跳过编译的代码上方,@ts-nocheck需要放在ts文件头部,表示忽略整个文件的编译检查,没有了编译检查,和写js基本没有本质区别,编译时的语法错误将不会得到任何提示。
fetch('url', {
// @ts-ignore
headers: {
attr: attr
}
})
扩展预设类型
在项目新建global.d.ts声明文件,并在tsconfig.json中将其include进去,可以扩展fetch的第二个参数类型RequestInit或者Headers,TypeScript会将项目中的类型和lib.dom.d.ts中的类型进行自动合并。为什么不能直接扩HeadersInit?因为HeadersInit是个复杂的联合类型,不是用interface声明的类型,无法进行自动合并。
interface Headers {
[key:string]: string | undefined
}
我们经常会遇到一些需求,需要扩展浏览器window对象或nodejs的全局变量,就可以使用类型扩展:
扩展window对象
interface Window {
// window 对象新增 hello方法
hello(): void
}
扩展Nodejs环境变量
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development'| 'production'
REACT_APP_TITLE: string
}
}
使用类型断言 as
类型断言是TypeScript中非常人性化的一个功能,因为他把类型推断交到开发者自己的手中。
TypeScript 允许你覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误。
// solution 1
fetch('url', {
headers: {
attr: attr as string
}
})
// solution 2
fetch('url', {
headers: {
attr: attr
} as HeadersInit
})
// solution 3
fetch('url', {
headers: {
attr: attr
} as Headers
})
类型断言通常是用来做类型收窄操作,但是TypeScript的类型断言可以使用多次,也就是双重断言,能够把一种类型转换成另一种毫不相关的类型,第一次类型断言是扩充类型,第二次则是收窄至想要的类型:
const s = Symbol()
const num = 1
// 下面的语法能够通过编译
const sum = num + (s as unknown as number)
const sum = num + (s as any as number)
使用非空断言 !
非空断言操作符会从变量中移除 undefined 和 null。这将告诉编译器变量在被调用的时候是已经赋值的状态,从而打消编译器判空的疑虑。
fetch('url', {
headers: {
attr: attr!
}
})
非空断言在一些处理可选属性的链式调用时非常有效,但比类型断言要精简的多!
与非空断言!对应的还有可选链?.操作符,可以更优雅的进行判空处理和安全的链式调用。
interface Window{
a?:{
b?:{
c?(): void
}
}
}
// 明确告诉编译器可以访问到`c`方法,编译检查通过,但是运行时可能会报错
window.a!.b!.c!()
// 只有`c`方法存在时才会被安全调用
window.a?.b?.c?.()
总结
TypeScript虽然拥有强大的静态类型推断系统,但是依然提供了很多编译选项,类型检查的严格程度由开发者自行决定,开发者也可以使用ts提供的一些黑魔法魔改类型推断,但是在运行时可能会报错,在使用的时候应当谨慎一些,合理的定义类型。