为什么要兼容?
因为实际工作中,往往无法类型一致
假设我们现在需要设计一个接受参数为 一个对象包涵3个属性 的函数,但实际数据缺拥有更多属性,我们很容易写出以下代码:
1 2 3 4 5 6 7
| const data = { a: 1, b: 2, c: 3, d: 4 }
const newData = lodash.pick(data, ['a', 'b', 'c'])
fn(newData)
|
但是实际上我们并不会这么写,对于 JS程序员来说为什么要这么麻烦呢,虽然你只需要三个属性,我多传你几个,你不用不就好了,所以一般我们都会着那么写:
1 2 3 4 5
| const data = { a: 1, b: 2, c: 3, d: 4 }
fn(data)
|
所以 TS 需要适应这种习惯,他不能去纠正别人,如果故意把别人的习惯打破,那就很难去做推广
什么是类型兼容?
简单来说就是你就有的,我都有,则我们代替你,即:
y 有的,x都有,则 x 兼容 y
基本类型的兼容
1 2 3
| type A = string | number
const a: A = 'hi'
|
此时,字符串 ‘hi’范围更小缺兼容了范围更大的,,似乎有点违背上面的理论,此时我们用集合的方式来理解会更好

结论:子集当然可以赋值给父集
普通对象的兼容
1 2 3 4 5 6 7 8 9 10 11 12
| type Person = { name: string; age: number; }
const user = { name: 'frank', age: 18, id: 1, email: 'qq.com', } const p: P = user
|
如果用上面的结论,子集 => 父集,那么对于对象来说, Person 和 user 那个范围更小呢?
其实通过逻辑推断来说,user 是更小的,因为属性多就是限制条件多,满足条件的就少,其实还是满足上述结论的,
但是为了方便记忆,我们可以得出对象的结论:属性多的可以赋值给属性少的
函数的兼容
相比下来函数会更复杂一点,函数不仅有属性,还包括参数和返回值
参数个数
直接上代码测试:
1 2 3 4 5 6 7 8 9
| const 接受一个参数的函数 = (n :number) => { console.log(n) } const 接受两个参数的函数 = (n: number, s: string) => { console.log(n, s) }
接受一个参数的函数 = 接受两个参数的函数 接受两个参数的函数 = 接受一个参数的函数
|
很容易得出结论:参数少的兼容参数多的
如果还没理解的话,可以结合实际来看,我们经常在函数传参的时候,只会少写参数个数,比如在 axios 的调用、事件的绑定等等
1 2 3 4 5 6
| const button = document.getElementById('submit') const fn = (e: MouseEvent) => console.log(e)
button.addEventListener('click', fn) button.addEventListener('click', fn, false) button.addEventListener('click', fn, true)
|
在 JS 眼中,参数少传,问题不大,所以少写参数是很常见的
当然也许有杠精会说,我多传参数也问题不大啊。那么再想想看,实际你会去多传参数么?!基本不会吧~
参数类型
直接上代码测试:
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface MyEvent { target: string; } interface MyMouseEvent extends MyEvent { x: number; y: number; }
const listener = (e: MyEvent) => console.log(e.target) const listenerMouse = (e: MyMouseEvent) => console.log(e.x, e.y)
listenerMouse = listener listener = listenerMouse
|
很容易得出结论:对参数要求少的兼容对参数要求多的,与普通对象的兼容相反
因为相比普通对象来说,函数的参数是要求别人的,所以和普通对象相反也能理解
我们再来看一个实际的栗子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface Event { target: number; } interface MyMouseEvent extends Event { x: number; y: number; } function listenEvent(eventType: string, handler: (n: Event) => void) { }
listenEvent('click', (e: MyMouseEvent) => console.log(e.x, x.y))
listenEvent('click', (e: Event) => console.log((e as MyMouseEvent).x, (e as MyMouseEvent).y))
listenEvent('click', ((e: MyMouseEvent) => console.log(e.x, x.y)) as (e: Event) => void)
|
但是可以通过 TS 的配置让上面的代码正常运行
1 2 3 4 5 6
| { "compilerOptions": { "strictFunctionTypes": false } }
|
特殊类型
直接上文档

加餐
顶类型和底类型
这两个概念不是 TS 的内容,但却是类型系统的知识
先上理论:底部类型可以赋值给顶部类型,用一张图来表示:

写在最后
虽然推论了这么多,但是你其实都不用记忆,什么小的兼容大的,少的兼容多的…
因为实际一使用,TS 就会你报错提示你