TypeScript基础语法

TypeScript学习

1.基础类型

TypeScript和JavaScript支持的数据类型差不多

  • 布尔:boolean

let flag: boolean = false
  • 数:number

可以表示整数,浮点数,支持2进制,8进制,10进制,16进制

let num1: number = 1
  • 字符串:string

let str = "a"
let str2 = `${a}`
  • 数组:Array 或者 number[]

  • 元组: tuple

let a: [number, string] = [1, "a"]
  • 枚举enum

enum Color { Red, Green, Yellow, Purple }
enum Test { Red, Green=1, Yellow }
// Test.Red = 1, Test.Green = 1, Test.Yellow = 2

注意枚举类型最好不要赋值为字符串,否则可能会产生意想不到的情况

  • any

表示类型不确定,或者在一个数组中有多种类型,你只知道一部分类型,不能确定全部就可以用any

  • void

如果一个函数没有返回值,则可以用void来定义,如果一个类型为void,那么只能被赋值为undefined或者null

  • null和undefined

当我们配置了:strickNullChecks:false,就可以将null和undefined赋值给任何类型,但是这样做不太妥,我们容易忽略null和undefined类型,会偏离预期,所以最好是设置为true。

  • never

表示永远不存在的值,你可能会觉得很奇怪,不存在还要这个干啥???
一般的应用场景:
① 抛出异常,没有返回值的函数表达式。
② 无法到达终点的函数。
③ switch的default情况设置为never可以帮助我们检查漏掉的case类型。

2. 泛型

当我们想要根据实际的情况来决定变量的类型时,我们就可以使用泛型,这个东东在java中也有,
好处
就是函数和类可以轻松地支持多种类型,增强程序的扩展性,不用谢多余的函数重载,灵活控制类型之间的约束。
注意点:
静态成员不能引用类类型参数

class Person {
    static name:string;
}

2.1 泛型变量

function identity(arg: T): T {
    return arg;
}

但是如果是这样的:

function identity(arg: T): T {
    return arg.length;
}

则会报错,因为编译器无法知道T是什么类型,万一是number,那么它一定没有长度,我们可以改写:

function identity(arg: T[]): T[] {
    return arg.length;
}
// or
function identity(arg: Array): T[] {
    return arg.length;
}

2.2 泛型函数和接口

根据传入的类型,返回相应的类型对应的值

function log(value: T): T {
    return value
}

也可以使用type来定义

type Log = (value:T) => T
let myLog: Log = log

也可以使用interface

interface Log {
    (value: T): T
}
let myLog: Log = log
myLog(1)

2.3 泛型类和泛型约束

我们也可以对class使用泛型

class Person {
    name: T;
    sayHi: (x: T) => `hi,${x}`
}

拿上面举的例子,我们想要获取传入值的长度,那我们可以通过约束类型的方式来实现

type Length {
    length: number;
}
function log(value: T):T {
    return value
}

3. 类型推断

当我们给一个变量直接赋值的时候,比如let a = 1,那么a自动就被认为是number类型,这也算是一种推论;
我们也可以使用as:

interface Foo { bar: number }
let foo = { } as Foo;
foo.bar = 1;

但是使用as容易遗漏,除非非常确定的情况,最好的方式:

let foo:Foo = {
    bar: 1;
}

4. 类型兼容

当一个类型Y可以被赋值给另一个类型X时,就可以说类型X兼容Y

X(目标类型) = Y(源类型)

4.1 接口兼容性

只要Y具备了X所有属性那么,X兼容Y,即X = Y,属性少的兼容属性多的

interface X {
    a: any;
}
interface Y {
    a: any;
    b: any;
}
let x: X = { a: 1 };
let y: Y = { a: 1, b: 2 };
x = y; //成功
y = x; //失败

4.2 函数兼容性

①参数个数匹配

函数参数多的兼容参数少的,与上面接口的正好相反

type H = ({a: number, b: number}) => void
function h(handler: H) {
    return handler;
}
  • 传一个参数(成功)

    h({a: 1} => {})

  • 传两个参数(完全没问题)

    h({a: 1, b: 2} => {})

  • 传多个参数(大于固定的参数个数2)(失败了=.=)

    h({a: 1, b: 2, c: 3} => {})

但是如果有可选参数和剩余参数的情况呢?情况入下图

let f1 = (p1: number, p2: number) => {}
let f2 = (p1?: number, p2?: number) => {}
let f3 = (...args: number[]) => {}
A/B (A兼容B) 固定参数 可选参数 剩余参数
固定参数f1 f1=f2 √ f1=f3 √
可选参数f2 × ×
剩余参数f3 f3=f1 √ f3=f2 √

对于可选参数,我们还可以设置"strictFunctionTypes": false,这样可选参数就可以都兼容固定参数和剩余参数了。
同理:

interface p1 { x:number, y: number }
interface p2 {x: number}
let f1 = (p: p1) => {}
let f2 = (p: p2) => {}
f1 = f2 // 成功
f2 = f1 //失败

也是参数多的兼容参数少的

②参数类型匹配

如果是string类型的参数肯定是无法与number类型参数匹配的

③返回值类型匹配

要求目标函数的返回值类型与源函数的一样,或者为其子类型。
即少的兼容多的

let f3 = () => ({ a: 1})
let f4 = () => ({ a: 1, b: 2})
f3 = f4 // 成功
f4 = f3 // 失败

4.3 枚举兼容性

枚举和number是相互兼容的,但是枚举与枚举之间是不相互兼容的

enum Fruit { Apple,  Banana };
enum Color { Red };
let fruit: Fruit.Apple = 1
let index:number = Fruit.Apple
let color: Color.Red = Fruit.Apple // 失败

4.4类兼容性

对于类的兼容,静态成员和构造函数是不参与比较的,只比较结构。

class A {
    id: number = 1;
    constructor(p: number, q:number) {}
}
class B {
    static name: string;
    id: number = 2
    constructor(p: number) {}
}

此时类A和B是相互兼容的,但是如果类中含有私有成员,就无法相互兼容,只有父类和子类的关系才会兼容。

4.5泛型兼容性

根据结构

interface Empty {
}
let x: Empty;
let y: Empty; 
x = y // 成功

x和y可以相互兼容,但是如果接口Empty有具体的结构

interface NotEmpty {
    data: T;
}
let x: Empty;
let y: Empty; 
x = y // 失败

当泛型被指定了具体的类型就不兼容了,但是如果这两个泛型函数定义相同,那么也是可以兼容的
总之:
结构之间兼容: 成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的

5. 类型保护

定义:在特定的区域中保证变量属于某种确定的类型,在此区块中放心引用此类型的属性或调用方法
①instanceof
直接判断是否属于某个类型,举个例子:

if(a instanceof Java) {
    Java.helloJava()
} else {
    a.hellJs()
}

②in(判断某个睡醒是否属于某个变量)

if('name' in Person) { ... } else { ... }

③typeof
这个就是普通的类型判断

if(typeof x === 'string') { 
    x.length 
} else { ... }

④is

function isString1(a: any):a is String {
    return typeof a === "string";
}

funciton isString2(a: any): boolean {
    return typeof a === "string";
}

function example(foo: any) {
    if(isString1(foo)) {
        console.log(foo.toFixed(1))  // 编译出错
    }
    if(isString2(foo)) {
        console.log(foo.toFixed(1)) // 通过
    }
    console.log(f.toFixed(1))  // 通过
}

这边有两种写法,同样都是判断一个变量是否为String类型,区别在于使用is会进一步缩小变量的类型,而且类型保护的作用域仅仅在if后的块级作用域中生效。

6. ts高级类型

6.1交叉类型

将多个类型合并成一个类型,新类型拥有所有类型特性

interface Dog {
    run():void
}
interface Cat {
    jump(): void
}
let pet: Dog & Cat = { run(){}, jump(){} }

6.2联合类型

声明的类型不确定,可能为多个类型中的一个

let a: number | string = "a"
let b: 'a' | 'b' | 'c' = "b"

当一个变量是两个类型的联合类型时,取交集类型
联合类型的使用场景:用于同一个行为的类型

interface Rectangle { kind: 'R', width: number, height: number }
interface Square { kind: 'S', size: number }
interface Circle { kind: 'C', r: number }
type Shape = Rectangle | Square | Circle
function getArea(s: Shape) {
    switch(s.kind) {
        case 'R': {
            return s.width * s.height
        };
        case 'S': {
            return s.size * s.size
        };
        case 'C': {
            return Math.PI * s.r ** 2
        };
        default: return ((e: never) => { throw new Error(e)})(s)
        // 如果此处出现变异错误,说明类型考虑得不完全
    }
}

6.3索引类型

 let obj = {
     a: 1,
     b: 2
 }
 function getValue(obj: any, keys: string[]){
    return keys.map(key => obj[key])
 }
 console.log(getValue(obj, ['a', 'b']))
 // [1, 2]
 console.log(getValue(obj, ['c']))
 //undefined

当我们想要根据key值获取value时候,最后一种情况的undefined没有被检查出来,我们需要使用索引类型来约束:

 let obj = {
      a: 1,
      b: 2
}
function getValue(obj: T, keys: K[]){
    return keys.map(key => obj[key])
}  
console.log(getValue(obj, ['a', 'b']))
console.log(getValue(obj, ['c']))

这时候最后一种就可以被检查出来了,因为对第二参数进行了约束,即K必须是T的key值。

2.映射类型

①Readonly
当我们想要让一个接口的所有的属性都变成仅可读,那么:

  interface Obj {
      a: number,
      b: string,
      c: boolean
    }
    type readOnlyObj = Readonly

内部实现的代码:

type Readonly = {
    readonly [P in keyof T]: T[P];
};

解析:遍历类型T中的所有key值,将所有值变成readonly
②Partial
同样,让所有类型变成可选的方法:

type partialObj = Partial

内部实现的代码:

type Partial = {
    [P in keyof T]?: T[P];
};

③Pick
抽取类型的,可以抽取出一个类型的子集

type PickObj = Pick

内部代码:

type Pick = {
    [P in K]: T[P];
};

以上成为同态,不会产生新的类型,都在obj内
④Record
引入其他key值,使用obj作为其类型,称为不同态

type RecordObj = Record<'x' | 'y', Obj>

结果:

type RecordObj = {
    x: Obj;
    y: Obj;
}

⑤Omit
可以使用Pick来实现

Omit = Pick

第一步:Exclude 获取T中所有key除了U以外的所有key;
第二步:抽取T中 Exclude属性值组成的对象

type person = { name: string, age: number, address: string }
let a: Omit = { age: 1, address: "fujian" }
// 等价:
Pick
= Pick
= {
    age: number,
    address: string
}

说白了,Omit就是取T对象类型中除了U属性组成的对象类型

3.条件类型

T extends U ? X : Y

解析:如果T可以被赋值给U,那么去类型X,否则取类型Y

type getName = T extends string ? "string" : "other"
getName // "string"

联合类型的条件类型:

(A | B) extends U ? X : Y
//可以拆分成
(A extends U? X : Y) | (B extends U? X : Y)
getName 
// 结果为: "String" | "other"

应用场景:
(1)可以筛选出无法被赋值到另一个类型的属性

type Diff = T extends U ? never : T
type t = Diff<"a" | "b" | "c", "a" | "e">
// 结果为 "b" | "c"

解析:

Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
= never | "b" | "c"
= "b" | "c"

(2)可以过滤类型

type NotNull = Diff
type t = NotNull 
// 结果为 string | number

内置类型
①Exclude

Exclude
type a = Exclude<"a" | "b", "a">
// 结果 "b"

抽取T中无法赋值给U的类型
②NonNullable

type b = NonNullable<"a" | "b" | null | undefined>

筛选掉null, undefined
③Extract

type c = Extract<"a" | "b", "a">

抽取T中可以赋值给U的类型,和Exclude相反
④ReturnType
获取参数返回值类型

type a = ReturnType<() => string>
//结果为 sring

内部代码:

type InstanceType any> = T extends new (...args: any) => infer R ? R : any;

infer待推断

你可能感兴趣的:(TypeScript基础语法)