问题
前段时间在 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
提供的一些黑魔法魔改类型推断,但是在运行时可能会报错,在使用的时候应当谨慎一些,合理的定义类型。