类型兼容与赋值

122 阅读4分钟

兼容的意义

实际工作时,往往无法做到类型一致

兼容的多种情况

  1. 简单类型的兼容---小赋值给大
    type A = string | number
    // 小赋值给大
    const a: A = 'hi'
    
  2. 普通对象的兼容---属性少的兼容属性多的,即属性多的赋值给属性少的
    // 例子 1
    type Person = {
        name: string,
        age: number
    }
    type User = {
        name: string,
        age: number,
        id: string,
        email: string
    }
    const user: User = {
        name: 'rourou',
        age: 18,
        id: '001',
        email: '001@001.com'
    }
    // 属性多的赋值给属性少的
    const person: Person = user
    
    // 例子 2
    const f1 = (p: Person): void => {
        console.log(p)
    }
    // User类型的对象作为参数 赋值给Person类型的对象
    f1(user)
    
  3. 接口的兼容---属性少的兼容属性多的,即属性多的赋值给属性少的
    // 例子 1 存在继承关系
    interface Person {
        name: string,
        age: number  
    }
    interface User extends Person {
        id: string,
        email: string
    }
    const user: User = {
        name: 'rourou',
        age: 18,
        id: '001',
        email: '001@001.com'
    }
    const p: Person = user
    
    // 例子 2 不存在继承关系
    interface 有左手的人 {
        left: string
    }
    interface 有双手的人 {
        left: string,
        right: string
    }
    const a_p: 有双手的人 = {
        left: 'l',
        right: 'r'
    }
    const l_p: 有左手的人 = a_p
    
  4. 函数的兼容
    • 参数个数不同时,多参数的函数兼容少参数的函数(即少参数的函数可以赋值给多参数的函数)(因为多数情况下,js程序员不会把所有的参数都传过去即只传需要的参数)
    // 例子 1 参数个数不同的函数的兼容
    type oneParam = (a: number) => void
    type twoParams = (a: number, b: string) => void
    
    let f1 = (a: number) => {
       console.log(a)
    }
    let f2 = (a:number, b: string) => {
       console.log(a, b)
    }
    let fOneParam: oneParam = f1
    fOneParam = f2 // 报错
    
    let fTwoParams: twoParams = f1
    fTwoParams = f2
    
    注: 这种情况很常见,例如给元素注册事件
    const button = document.getElementById('submit')
    const fn = (e: MouseEvent) => {
        console.log(e)
    }
    button.addEventListener('click', fn) // 第三个参数不传默认为 false
    button.addEventListener('click', fn, false) // 冒泡
    button.addEventListener('click', fn, true) // 捕获
    
    • 参数个数相同,但类型不同时,参数类型个数多的兼容类型个数少的(即参数类型个数少的可以赋值给参数类型个数多的)
    interface MouseEvent {
        target: string
    }
    interface MyMouseEvent extends MouseEvent {
        x: number,
        y: number
    }
    let f1 = (e: MouseEvent) => {
        console.log(e)
    }
    let f2 = (e: MyMouseEvent) => {
        console.log(e)
    }
    f1 = f2 // 报错 
    f2 = f1
    
    tip: 可以这样记忆,参数相当于凸出来的齿子的木块(齿子可以收回),所以参数多的可以赋值的参数少的(因为多的可以收回);而参数个数相同类型不同的函数 相当于凹进去齿子的木块,比如凹进去两个齿子和凹进去三个齿子,凸出来两个、三个的都可以赋值给凹进去两个的,但凸出来两个的没办法赋值给凹进来三个的。

在工作中的应用

interface Event {
    target: string
}
interface MyMouseEvent extends Event {
    x: number,
    y: number
}
function listenEvent(eventType: string, fn: (e: Event) => void) {
    // 一些操作
}

// 错误的函数调用
// 类型 “(e: MyMouseEvent) => void”的参数不能赋给类型“(e: Event) => void”的参数。  
// 参数“e”和“e” 的类型不兼容。  
// 类型“Event”缺少类型“MyMouseEvent”中的以下属性: x, y
// 即:参数个数相同,类型不同时,参数类型个数少的可以赋值给参数类型个数多的
// 可以在tsconfig.json文件中修改条件。这样就可以多的赋值给少的了
"compilerOptions": {
   "strictFunctionTypes": false
}
listenEvent('click', (e: MyMouseEvent) => {
    console.log(e.x, e.y)
})


// 正确的函数调用
listenEvent('click', (e: Event) => {
    // 断言后可以拿到 x y 的值
    console.log((e as MyMouseEvent).x, (e as MyMouseEvent).y)
})

  1. 函数返回值属性不同(同对象赋值)
let 返回值属性少集合大 = () => {
    return { name: 'rourou' }
}
let 返回值属性多集合小 = () => {
    return { name: 'rourou', age: 18 }
}
返回值属性多集合小 = 返回值属性少集合大
返回值属性少集合大 = 返回值属性多集合小

类型赋值.jpg

1. unknown 是所有类型的并集,并集不能直接用,需要类型收窄后才可以用。即 unknown 只能赋值给 anyunknown2. 类型小范围可以赋值给大范围
3. object 是个并集,不包含基本类型(非严格模式下:除了 nullundefined4. never 是最小的集合,可以赋值给任何类型
5. voidundefinednull 之间的赋值(根据 tsconfig.json 来配置)
6. undefined 可以赋值给 void,即没有返回值的函数可以 return undefined

6. unknown 是顶类型可以接受任何类型的赋值,never 是底类型不接受任何类型的赋值