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> 或者 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<T>(arg: T): T {
    return arg;
}

但是如果是这样的:

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

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

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

2.2 泛型函数和接口

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

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

也可以使用type来定义

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

也可以使用interface

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

2.3 泛型类和泛型约束

我们也可以对class使用泛型

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

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

type Length {
    length: number;
}
function log<T extends Length>(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<T> {
}
let x: Empty<number>;
let y: Empty<string>; 
x = y // 成功

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

interface NotEmpty<T> {
    data: T;
}
let x: Empty<number>;
let y: Empty<string>; 
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<T, K extends keyof T>(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<Obj>

内部实现的代码:

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

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

type partialObj = Partial<Obj>

内部实现的代码:

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

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

type PickObj = Pick<Obj, 'a' | 'b'>

内部代码:

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

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

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

结果:

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

⑤Omit
可以使用Pick来实现

Omit<T, U> = Pick<T, Exclude<keyof T, U>

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

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

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

3.条件类型

T extends U ? X : Y

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

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

联合类型的条件类型:

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

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

type Diff<T, U> = 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<T> = Diff<T, undefined | null>
type t = NotNull<string | number | undefined | null> 
// 结果为 string | number

内置类型
①Exclude

Exclude<T, U>
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<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

infer待推断

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343