首页 > 其他 > 详细

细说Typescript类型检查机制

时间:2020-09-10 14:50:26      阅读:88      评论:0      收藏:0      [点我收藏+]

上一篇文章我介绍了Typescript的基础知识,回顾一下,有基础数据类型、数组、函数、类、接口、泛型等,本节内容将述说一下Typescript为方便我们开发提供了一些类型检查机制。

 

类型检查机制

类型检查机制: Typescript编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。

作用:辅助开发,提高开发效率。主要有以下三点:

 

  1. 类型推断
  2. 类型兼容性
  3. 类型保护

1、类型推断

不需要指定变量的类型(函数的返回值类型), Typescript可以根据某些规则自动地为其推断出一个类型。

  • 基础类型推断。是ts中比较常见的类型推断。

 

// 没有设置类型时,TS会根据赋值的类型推断类型
let a1;     // 等价于 let a1: any let a
= 1;   // 等价于 let a: number let b = [1];  // 等价于 let b: number[] let c = [1, null];      // 等价于 let c: number[] let c1 = (x = 1) => {}   // 等价于 let c1: (x?: number) => void let d = (x = 1) => x + 1; // 等价于 let d: (x?: number) => number

 

  • 最佳通用类型推断
// 断言好处:改造旧代码会很有效,但避免滥用,需对上下文环境有充足的预判,否则会带来安全隐患
interface Foo {
    bar: number
}
// let foo = {}; // 报错
// let foo = {} as Foo; // 注意:不可滥用断言,若去掉bar属性,foo对象就没有按照接口的严格约定返回,就会遗漏
// foo.bar = 1; 
let foo: Foo = { // 最佳通用类型推断
    bar: 1
};
  • 总结:TS的类型推断可以为我们提供一些重要的辅助信息,但是需要合理使用

2、类型兼容性

当一个类型 y 可以被赋值给另一个类型 x 时,我们就可以说类型 x 兼容类型 y x 兼容 y : x (目标类型)= y(源类型);

简单理解:

接口之间兼容:成员少的兼容成员多的

函数之间兼容:参数多的兼容参数少的

2.1 类型兼容

// x 兼容 y : x (目标类型) =  y(源类型)
let s: string = ‘a‘
s = null; // 报错

上述的报错,在tsconfig.json 设置 strictNullChecks:false即可

2.2 接口兼容

接口之间兼容:成员少的会兼容成员多的

interface XX {
    a: any;
    b: any;
}
interface YY {
    a: any;
    b: any;
    c: any;
}
let xx: XX = {a:1, b:2}
let yy: YY = {a:1, b:2, c:3}

xx = yy;
// yy = xx; // 报错。注意:源类型必须要具备目标类型的必要属性。xx可以兼容yy类型,反之不行

2.3 函数兼容

判断两个函数是否兼容,一般会发生在相互赋值的场景下。

函数之间兼容:参数多的兼容参数少的

type Handler = (a: number, b: number) => void; // Handler 为目标类型,传入的参数为源类型
function hocf(handler: Handler){
    return handler
}
// 目标函数要兼容源函数需要同时满足以下三点:
// 1) 参数个数要求: 目标函数的个数要多于源函数参数的个数
    // 固定参数个数
    let handler1 = (a: number) => {};
    hocf(handler1);
    // let handler2 = (a: number, b: number, c: number) => {};
    // hocf(handler2); // 报错。目标函数的个数要多于源函数参数的个数
    
    // 不固定参数:可选参数和剩余参数: 需坚持三原则
    let l = (p1: number, p2: number) => {}
    let m = (p1?: number, p2?: number) => {}
    let n = (...args: number[]) => {}
    // - 1.固定参数可以兼容可选参数和剩余参数
    l = m;
    l = n;
    // - 2.可选参数不兼容固定参数和剩余参数,但设置 tsconfig strictFunctionTypes:false 属性,可预防报错
    m = n // 报错
    m = n // 报错
    // - 3.剩余参数可以兼容固定参数和可选参数
    n = l
    n = m
    
// 2) 参数类型要求
    // 基础类型接口
    let handler3 = (a: string) => {};
    // hocf(handler3); // 报错。类型不兼容

    // 对象类型接口:成员多的兼容成员少的,与接口成员兼容相反,可以看成参数,参数多的兼容参数少的
    interface Point3D {
        x: number;
        y: number;
        z: number;
    }
    interface Point2D {
        x: number;
        y: number;
    }
    // 参数个数相同,参数类型相同Object
    let p3d = (point: Point3D) => {}
    let p2d = (point: Point2D) => {}
    p3d = p2d
    p2d = p3d // 报错。设置 tsconfig strictFunctionTypes:false 属性,可预防报错
    // 函数参数之间可以相互赋值的情况被称为 参数双向协变。作用:可以把精确类型 赋值给 不精确类型,这样就不需要把不精确的类型断言成精确类型

// 3)  返回值类型。ts要求我们目标函数的返回值类型必须要和源函数返回值类型一致或者是其子类型
    // 成员少的兼容成员多的 
    let o = () => ({name: "Alice"});
    let p = () => ({name: "Alice", location: ‘Beijing‘});
    o = p;
    // p = o; // 报错

2.4 函数重载兼容

// 在重载中,目标函数的参数要多于源函数的参数
function overload(a: number, b: number): number // 列表中的函数:目标函数
function overload(a: string, b: string): string // 列表中的函数:目标函数
function overload(a: any, b: any): any {} // 具体实现:源函数
// function overload(a: any, b: any, c: any): any {} // 报错。在重载中,目标函数的参数要多于源函数的参数
// function overload(a: any, b: any) {} // 报错。返回值参数类型不兼容

2.5 枚举兼容

enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
// 枚举类型和数字类型是完全可以互相兼容和赋值的
let fruit: Fruit.Apple = 3; 
let no: number = Fruit.Apple;
// 枚举类型之间是不兼容的
// let color: Color.Red = Fruit.Apple; // 报错

2.6 类兼容

类兼容和接口兼容相似,都是只比较结构;

1. 构造函数constructor、和静态成员static是不作为比较的;

2. 如果两个类具有相同的实例成员,这两个类之间也是兼容的;

3. 类中有私有成员 private,两个类是不兼容的,只有父类和子类是兼容的;

class A {
    constructor(p: number, q: number){}
    id: number = 1
    // private name: string = ‘‘// 3. 报错
}
class B {
    static s = 1;
    constructor(p: number){}
    id: number = 2
    // private name: string = ‘‘ // 3. 报错
}

let aa = new A(1,2)
let bb = new B(1)
// 2. 都具有id属性,所以相互兼容
aa = bb; 
bb = aa;
// 3. 父类和子类是兼容的
class An extends A { }
let an = new An(1,2)
aa = an;
an = aa;

2.7 泛型兼容

// - 不会报错
interface Empty<T>{  }
let obj1: Empty<number> = {  }
let obj2: Empty<number> = {  }
obj1 = obj2;
// - 会报错 只有接口参数T被接口使用的时候,才会影响泛型的兼容性
// interface Empty<T>{
//     value: T
// }
// let obj1: Empty<number> = {}
// let obj2: Empty<number> = {}
// obj1 = obj2;

// 2. 泛型函数
let loog1 = <T>(x: T): T => {
    console.log(‘x‘)
    return x
}
let loog2 = <U>(y: U): U => {
    console.log(‘y‘)
    return y
}
loog1 = loog2; // 两个泛型函数的定义相同,并没有指定类型参数,泛型函数也是兼容的

 

2.8 总结

1、考虑类型兼容是因为ts允许我们把一些类型不同的变量相互赋值,虽然在某种程度上讲,会产生不可靠的行为,但这个特性却增加了语言的灵活性

2、类型兼容的使用场景:广泛存在接口、函数、类中 演示

3、类型保护

定义: Typescript能够在特定的区块(类型保护区块)中保证变量属于某种确定的类型。 可以在此区块中放心地引用此类型的属性,或者调用此类型的方法。

换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。

enum Type { Strong, Week }
class Java {
    helloJava(){
        console.log(‘Hello Java‘)
    }
    java: any
}
class JavaScript {
    helloJavaScript(){
        console.log(‘Hello JavaScript‘)
    }
    javascript: any
}
function getLanguage(type: Type, x: string|number){
    let lang = type === Type.Strong ? new Java() : new JavaScript();
    // 用类型断言解决返回值不确定的情况,可读性差
    if((lang as Java).helloJava){
        (lang as Java).helloJava()
    } else {
        (lang as JavaScript).helloJavaScript()
    }
    // console.log(lang, ‘lang‘)
    return lang;
}
getLanguage(Type.Strong, ‘11‘)
 

上述的解决方案显然不是最佳方案,有以下四种方案可以解决类型保护:

3.1 方案1: instanceof

function getLanguage(type: Type, x: string|number){
    let lang = type === Type.Strong ? new Java() : new JavaScript();
   if(lang instanceof Java){
        lang.helloJava()
    } else {
        lang.helloJavaScript()
    }
    // console.log(lang, ‘lang‘)
    return lang;
}

3.2 方案2: in

function getLanguage(type: Type, x: string|number){
    let lang = type === Type.Strong ? new Java() : new JavaScript();
  if(‘java‘ in lang){
        lang.helloJava()
    } else {
        lang.helloJavaScript()
    }
    // console.log(lang, ‘lang‘)
    return lang;
}

3.3 方案3: typeof

function getLanguage(type: Type, x: string|number){
    let lang = type === Type.Strong ? new Java() : new JavaScript();
  // x也是联合类型,typeof类型保护,可以判断出基本类型。
    if(typeof x === ‘string‘){
        x.length
    } else {
        x.toFixed
    }
    // console.log(lang, ‘lang‘)
    return lang;
}

3.4 方案4: 类型保护函数

function getLanguage(type: Type, x: string|number){
    let lang = type === Type.Strong ? new Java() : new JavaScript();
  if(isJava(lang)){
        lang.helloJava()
    }else{
        lang.helloJavaScript()
    }

    // console.log(lang, ‘lang‘)
    return lang;
}

定义一个类型保护函数:

// 类型保护函数,可以传递任何值给 isJava 函数,用来判断它是不是 Java。isJava 函数与普通函数的最大区别是,该函数的返回类型是 lang is Java,这就是 "类型谓词"。
function isJava(lang: Java | JavaScript): lang is Java{
// 类型谓词可参考:https://segmentfault.com/a/1190000022883470
return (lang as Java).helloJava !== undefined // 返回结果是 true,那么当前判断的 lang 变量值的类型是 Java 类型。
}

类型保护作用:

1. 可以提前对类型做预判

2. 返回类型谓词,如 lang is Java;

3. 包含可以准确确定给定变量类型的逻辑语句,如 (lang as Java).helloJava !== undefined。

 

3.5 总结

不同的判断方法有不同的使用场景:

typeof:判断一个变量的类型(多用于基本类型);

instanceof:判断一个实例是否属于某个类;

in:判断一个属性是否属于某个对象;

类型保护函数:某些判断可能不是一条语句能够搞定的,需要更多复杂的逻辑,适合封装到一个函数内。

结合ts类型检查机制再配合vscode 自动补全和自动修复功能,能够极大的提高我们的开发效率!

 

4、结尾

截止本节,Typescript的基础知识已经介绍差不多了,后期如有时间会继续更新TS的进阶高级部分,感谢您的阅读~

细说Typescript类型检查机制

原文:https://www.cnblogs.com/xdfapp/p/13645426.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!