我正在参加「掘金·启航计划」
为什么要有类型兼容
因为实际工作中,往往无法类型一致。
const config = {
a: 1, b: 2, c: 3, d: 4
}
// 如果类型只接受 3 个 key, 那么 d 就是多余了,传过去的话会导致类型不匹配,就会报错
// 于是 使用 lodash.pick
const newConfig = lodash.pick(config, ['a', 'b', 'c']) // 从 4 个 key 里拿到 3 个 key
runTask(newConfig) // runTask 只接受传的 3 个 key 的对象
// 实际上我们不会这么写
// 大家的习惯都是直接把 4 个选项硬塞给你,为了更好的推广 TS
// 于是当只需要 3 个属性时,传 4 个也兼容
const config = {
a: 1, b: 2, c: 3, d: 4
}
runTask(config)
简单类型和普通对象如何兼容
你有的,我都有,则我能代替你
变量 y 有的,x 都有,则 x 兼容 y
简单类型
type A = string | number
// 从集合的角度,如果你给我传一个范围更小的,实际上是可以兼容范围更大的
let a: A = 'hi'
当你需要一个父级的时候,我用一个子集代替你
普通对象的兼容
// user 兼容 p, 故将 user 赋值给 p 不报错
// user 小, Person 大
type Person = {
name: string;
age: number;
}
// 属性多就是限制条件多【
let user = {
name: 'hone',
age: 18,
id: 1,
email: 'xxx@gmail.com'
}
let p: Person
// 限制多的,赋值给限制小的
p = user
let user = {
name: '1',
age: 18,
id: 1,
email: 'xxx'
}
// 类型获取
// 这里的 typeof 是 TS 的,type 开头的是 TS 代码,最终这句话会被擦除,这个 typeof 也会被擦除
type User = typeof user
// 可以先写对象,再去获取它的类型
type Person = {
name: string
age: number
}
type User = {
name: string,
age: number,
id: number,
email: string
}
let p:Person = {
name: 'hone', age: 18
}
// 缺少属性 无法赋值
let u: User = p
user 作为参数也不报错
const f1 = (p: Person) => {
console.log(p)
}
f1(user)
接口如何兼容
父子接口
interface 父接口 {
x: string
}
interface 子接口 extends 父接口 {
y: string
}
let objectChiild: 子接口 = {
x: 'yes',
y: 'yes'
}
let objectParent: 父接口
objecParent = objectChild
// 如果两个接口之间完全没有关系 是可以赋值的
// 两个没有继承关系的接口
interface 有左手的人 {
left: string
}
interface 有双手的人 {
left: string
right: string
}
let person: 有双手的人 = {
left: 'yes',
right: 'yes'
}
let personLeft: 有左手的人 = person
// 但是反过来就不行,因为你的功能是缺少的
函数如何兼容
函数包括参数和返回值
参数的个数不同,能兼容吗?
let 接受一个参数的函数 = (a: number) => {
console.log(a)
}
let 接受两个参数的函数 = (x: number, y: string) => {
console.log(x, y)
}
接受两个参数的函数 = 接受一个参数的函数 // OK
接受一个参数的函数 = 接受两个参数的函数 // 报错
// 为什么容忍参数变少 ???
在 JS 中,少写参数是很常见的
const button = document.getElementById(sumbit)
const fn = (e: MouseEvent) => console.log(e)
button.addEventListener('click', fn)
button.addEventListener('click', fn, false)
button.addEventListener('click', fn, true)
let items = [1, 2, 3]
items.forEach((item, index, array) => console.log(item))
items.forEach((item) => console.log(item))
// 参数少传 问题不大,少写可以做默认参数处理
多参数 <= 少参数,少的可以赋值给多的
参数类型不同,能兼容吗?
interface MyEvent {
target: string
}
interface MyMouseEvent extends Myevent {
x: number
y: number
}
let listener = (e: MyEvent) => console.log(e.target)
let mouseListener = (e.MyMouseEvent) => console.log(e.x, e.y)
mouseListener = listener
listener = mouseListener // 报错
对参数要求多 <= 对参数要求少,对参数要求少的赋值给参数要求多的
interface MyEvent {
timestamp: number
}
interface MyMouseEvent extends MyEvent {
x: number;
y: number;
}
function listenEvent(eventType: string, fn: (n: MyEvent) => void) {
/*...*/
}
// 我们希望这样用
listenEvent('click', (e: MyMouseEvent) => console.log(e.x + "," + e.y))
// 但只能这样用
listenEvent('click', (e: MyEvent) => {
//const x = (e as number) // 这样就会报错
const x = (e as unknown as number) // 这样就不会报错,因为 TS 就是想警告你 ,如果想要逃脱检查,就要写一个很长很长的代码
// 主观的告诉 TS 这里就不用去报错了,每一次用 as 的时候,如果出了问题就自己负责
console.log((e as MyMouseEvent).x + "," + (e as MyMouseEvent).y)
})
// 还可以这么用
listentEvent('click', ((e: MyMouseEvent) =>
// 也可以把整个函数 as
cosole.log(e.x + "," + e.y)) as (e: MyEvent) => void)
// 这个就太离谱了
// 这里是不可以传 number 的
listenEvent('click', (e: number) => console.log(e)) // 我虽然可以让你随便断言,但是也不可以这样瞎断言
TS 经常要做一个折中,到底要不要严格,如果太严格了会不会写代码写不出来,于是就松散一点。
在项目中有一个 tsconfig.json 文件
{
"compilerOptions": {
"strictFunctionTypes": false // 把这个关了,就可以单独对某一个类型不检查
}
}
加了以上代码后, listenEvent('click', (e: MyMouseEvent) => console.log(e.x + "," + e.y)) 中的 MyMouseevent 就不会报错。
如果还觉得的严格,可以写 const x = (e as unknown as number)
赋值表
返回值类型不同能兼容吗 ?
let 返回值属性少集合大 = () => {
return { name: "Alice" }
}
let 返回值属性多集合小 = () => {
return { name: "Alice", location: "Seattle" }
}
返回值属性少集合大 = 返回值属性多集合小 // OK
返回值属性多集合小 = 返回值属性少集合大 // 报错:"location" is missing
大 <= 小
限制少 <= 限制多
特殊类型
any -> 表示 any 可以赋值给
红线圈起来的:确定需要单独记忆的。
黄线圈起来的:需要根据配置来定的,在 tsconfig.json 中 写 strictNullChecks: false 进行松散类型检查。
灰色涂起来的根据理论分析就可以得出。
Top Type 与 Bottom Type
top type: unknown // 顶类型
string|number {} object
{name}
string undefined null number {name, age}
bottom type: never // 底类型 下面的赋值给上面的
any 是一个错误关闭器,除了 any 不能 赋值给 never
也有认为 TS 的 top type 是 unknown 和 any,文章链接, 都可以的,自己能理解的通就行。
一些思考
let b: any = await axios(...)
// any 永远都不需要去断言
b.xxx()
c.yyy()
let a = b // a 也变成了 any
// 当你在项目里用了一个 any, 它会生出一窝的 any,一窝又一窝。
应该用什么?
// 使用 unknown
let b: unknown = await axios(...)
// 当你用 unknown 的时候,没有做类型收窄,你会发现你什么方法都用不了
// unknown 会提醒你去看一下类型,不要随便调用
// unknown 你在用的时候你每一次都要去重新断言
(b as {name: string, id: string}).toFixed()
// 树形结构 所有 JSON 的数据类型
type JSONValue =
| string
| number
| null
| boolean
| JSONValue[]
| { [k: string]: JSONValue }